2021-04-22 17:41:21 +02:00
// SPDX-License-Identifier: BSD-3-Clause
/*
* Simple Landlock sandbox manager able to launch a process restricted by a
* user - defined filesystem access control policy .
*
* Copyright © 2017 - 2020 Mickaël Salaün < mic @ digikod . net >
* Copyright © 2020 ANSSI
*/
# define _GNU_SOURCE
# include <errno.h>
# include <fcntl.h>
# include <linux/landlock.h>
# include <linux/prctl.h>
# include <stddef.h>
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <sys/prctl.h>
# include <sys/stat.h>
# include <sys/syscall.h>
# include <unistd.h>
# ifndef landlock_create_ruleset
2022-05-06 18:05:13 +02:00
static inline int
landlock_create_ruleset ( const struct landlock_ruleset_attr * const attr ,
const size_t size , const __u32 flags )
2021-04-22 17:41:21 +02:00
{
return syscall ( __NR_landlock_create_ruleset , attr , size , flags ) ;
}
# endif
# ifndef landlock_add_rule
static inline int landlock_add_rule ( const int ruleset_fd ,
2022-05-06 18:05:13 +02:00
const enum landlock_rule_type rule_type ,
const void * const rule_attr ,
const __u32 flags )
2021-04-22 17:41:21 +02:00
{
2022-05-06 18:05:13 +02:00
return syscall ( __NR_landlock_add_rule , ruleset_fd , rule_type , rule_attr ,
flags ) ;
2021-04-22 17:41:21 +02:00
}
# endif
# ifndef landlock_restrict_self
static inline int landlock_restrict_self ( const int ruleset_fd ,
2022-05-06 18:05:13 +02:00
const __u32 flags )
2021-04-22 17:41:21 +02:00
{
return syscall ( __NR_landlock_restrict_self , ruleset_fd , flags ) ;
}
# endif
# define ENV_FS_RO_NAME "LL_FS_RO"
# define ENV_FS_RW_NAME "LL_FS_RW"
# define ENV_PATH_TOKEN ":"
static int parse_path ( char * env_path , const char * * * const path_list )
{
int i , num_paths = 0 ;
if ( env_path ) {
num_paths + + ;
for ( i = 0 ; env_path [ i ] ; i + + ) {
if ( env_path [ i ] = = ENV_PATH_TOKEN [ 0 ] )
num_paths + + ;
}
}
* path_list = malloc ( num_paths * sizeof ( * * path_list ) ) ;
for ( i = 0 ; i < num_paths ; i + + )
( * path_list ) [ i ] = strsep ( & env_path , ENV_PATH_TOKEN ) ;
return num_paths ;
}
2022-05-06 18:05:12 +02:00
/* clang-format off */
2021-04-22 17:41:21 +02:00
# define ACCESS_FILE ( \
LANDLOCK_ACCESS_FS_EXECUTE | \
LANDLOCK_ACCESS_FS_WRITE_FILE | \
LANDLOCK_ACCESS_FS_READ_FILE )
2022-05-06 18:05:12 +02:00
/* clang-format on */
2022-05-06 18:05:13 +02:00
static int populate_ruleset ( const char * const env_var , const int ruleset_fd ,
const __u64 allowed_access )
2021-04-22 17:41:21 +02:00
{
int num_paths , i , ret = 1 ;
char * env_path_name ;
const char * * path_list = NULL ;
struct landlock_path_beneath_attr path_beneath = {
. parent_fd = - 1 ,
} ;
env_path_name = getenv ( env_var ) ;
if ( ! env_path_name ) {
/* Prevents users to forget a setting. */
fprintf ( stderr , " Missing environment variable %s \n " , env_var ) ;
return 1 ;
}
env_path_name = strdup ( env_path_name ) ;
unsetenv ( env_var ) ;
num_paths = parse_path ( env_path_name , & path_list ) ;
if ( num_paths = = 1 & & path_list [ 0 ] [ 0 ] = = ' \0 ' ) {
/*
* Allows to not use all possible restrictions ( e . g . use
* LL_FS_RO without LL_FS_RW ) .
*/
ret = 0 ;
goto out_free_name ;
}
for ( i = 0 ; i < num_paths ; i + + ) {
struct stat statbuf ;
2022-05-06 18:05:13 +02:00
path_beneath . parent_fd = open ( path_list [ i ] , O_PATH | O_CLOEXEC ) ;
2021-04-22 17:41:21 +02:00
if ( path_beneath . parent_fd < 0 ) {
fprintf ( stderr , " Failed to open \" %s \" : %s \n " ,
2022-05-06 18:05:13 +02:00
path_list [ i ] , strerror ( errno ) ) ;
2021-04-22 17:41:21 +02:00
goto out_free_name ;
}
if ( fstat ( path_beneath . parent_fd , & statbuf ) ) {
close ( path_beneath . parent_fd ) ;
goto out_free_name ;
}
path_beneath . allowed_access = allowed_access ;
if ( ! S_ISDIR ( statbuf . st_mode ) )
path_beneath . allowed_access & = ACCESS_FILE ;
if ( landlock_add_rule ( ruleset_fd , LANDLOCK_RULE_PATH_BENEATH ,
2022-05-06 18:05:13 +02:00
& path_beneath , 0 ) ) {
fprintf ( stderr ,
" Failed to update the ruleset with \" %s \" : %s \n " ,
path_list [ i ] , strerror ( errno ) ) ;
2021-04-22 17:41:21 +02:00
close ( path_beneath . parent_fd ) ;
goto out_free_name ;
}
close ( path_beneath . parent_fd ) ;
}
ret = 0 ;
out_free_name :
2021-04-28 14:38:52 -07:00
free ( path_list ) ;
2021-04-22 17:41:21 +02:00
free ( env_path_name ) ;
return ret ;
}
2022-05-06 18:05:12 +02:00
/* clang-format off */
2021-04-22 17:41:21 +02:00
# define ACCESS_FS_ROUGHLY_READ ( \
LANDLOCK_ACCESS_FS_EXECUTE | \
LANDLOCK_ACCESS_FS_READ_FILE | \
LANDLOCK_ACCESS_FS_READ_DIR )
# define ACCESS_FS_ROUGHLY_WRITE ( \
LANDLOCK_ACCESS_FS_WRITE_FILE | \
LANDLOCK_ACCESS_FS_REMOVE_DIR | \
LANDLOCK_ACCESS_FS_REMOVE_FILE | \
LANDLOCK_ACCESS_FS_MAKE_CHAR | \
LANDLOCK_ACCESS_FS_MAKE_DIR | \
LANDLOCK_ACCESS_FS_MAKE_REG | \
LANDLOCK_ACCESS_FS_MAKE_SOCK | \
LANDLOCK_ACCESS_FS_MAKE_FIFO | \
LANDLOCK_ACCESS_FS_MAKE_BLOCK | \
2022-05-06 18:10:59 +02:00
LANDLOCK_ACCESS_FS_MAKE_SYM | \
LANDLOCK_ACCESS_FS_REFER )
2022-05-06 18:05:12 +02:00
/* clang-format on */
2022-09-23 17:42:05 +02:00
# define LANDLOCK_ABI_LAST 2
2021-04-22 17:41:21 +02:00
int main ( const int argc , char * const argv [ ] , char * const * const envp )
{
const char * cmd_path ;
char * const * cmd_argv ;
2022-05-06 18:10:59 +02:00
int ruleset_fd , abi ;
__u64 access_fs_ro = ACCESS_FS_ROUGHLY_READ ,
access_fs_rw = ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE ;
2021-04-22 17:41:21 +02:00
struct landlock_ruleset_attr ruleset_attr = {
2022-05-06 18:10:59 +02:00
. handled_access_fs = access_fs_rw ,
2021-04-22 17:41:21 +02:00
} ;
if ( argc < 2 ) {
2022-05-06 18:05:13 +02:00
fprintf ( stderr ,
" usage: %s= \" ... \" %s= \" ... \" %s <cmd> [args]... \n \n " ,
ENV_FS_RO_NAME , ENV_FS_RW_NAME , argv [ 0 ] ) ;
fprintf ( stderr ,
" Launch a command in a restricted environment. \n \n " ) ;
2021-04-22 17:41:21 +02:00
fprintf ( stderr , " Environment variables containing paths, "
" each separated by a colon: \n " ) ;
2022-05-06 18:05:13 +02:00
fprintf ( stderr ,
" * %s: list of paths allowed to be used in a read-only way. \n " ,
ENV_FS_RO_NAME ) ;
fprintf ( stderr ,
" * %s: list of paths allowed to be used in a read-write way. \n " ,
ENV_FS_RW_NAME ) ;
fprintf ( stderr ,
" \n example: \n "
" %s= \" /bin:/lib:/usr:/proc:/etc:/dev/urandom \" "
" %s= \" /dev/null:/dev/full:/dev/zero:/dev/pts:/tmp \" "
2022-09-23 17:42:05 +02:00
" %s bash -i \n \n " ,
2022-05-06 18:05:13 +02:00
ENV_FS_RO_NAME , ENV_FS_RW_NAME , argv [ 0 ] ) ;
2022-09-23 17:42:05 +02:00
fprintf ( stderr ,
" This sandboxer can use Landlock features "
" up to ABI version %d. \n " ,
LANDLOCK_ABI_LAST ) ;
2021-04-22 17:41:21 +02:00
return 1 ;
}
2022-05-06 18:10:59 +02:00
abi = landlock_create_ruleset ( NULL , 0 , LANDLOCK_CREATE_RULESET_VERSION ) ;
if ( abi < 0 ) {
2021-04-22 17:41:21 +02:00
const int err = errno ;
2022-05-06 18:10:59 +02:00
perror ( " Failed to check Landlock compatibility " ) ;
2021-04-22 17:41:21 +02:00
switch ( err ) {
case ENOSYS :
2022-05-06 18:05:13 +02:00
fprintf ( stderr ,
" Hint: Landlock is not supported by the current kernel. "
" To support it, build the kernel with "
" CONFIG_SECURITY_LANDLOCK=y and prepend "
" \" landlock, \" to the content of CONFIG_LSM. \n " ) ;
2021-04-22 17:41:21 +02:00
break ;
case EOPNOTSUPP :
2022-05-06 18:05:13 +02:00
fprintf ( stderr ,
" Hint: Landlock is currently disabled. "
" It can be enabled in the kernel configuration by "
" prepending \" landlock, \" to the content of CONFIG_LSM, "
" or at boot time by setting the same content to the "
" \" lsm \" kernel parameter. \n " ) ;
2021-04-22 17:41:21 +02:00
break ;
}
return 1 ;
}
2022-09-23 17:42:05 +02:00
2022-05-06 18:10:59 +02:00
/* Best-effort security. */
2022-09-23 17:42:05 +02:00
switch ( abi ) {
case 1 :
/* Removes LANDLOCK_ACCESS_FS_REFER for ABI < 2 */
ruleset_attr . handled_access_fs & = ~ LANDLOCK_ACCESS_FS_REFER ;
fprintf ( stderr ,
" Hint: You should update the running kernel "
" to leverage Landlock features "
" provided by ABI version %d (instead of %d). \n " ,
LANDLOCK_ABI_LAST , abi ) ;
__attribute__ ( ( fallthrough ) ) ;
case LANDLOCK_ABI_LAST :
break ;
default :
fprintf ( stderr ,
" Hint: You should update this sandboxer "
" to leverage Landlock features "
" provided by ABI version %d (instead of %d). \n " ,
abi , LANDLOCK_ABI_LAST ) ;
2022-05-06 18:10:59 +02:00
}
2022-09-23 17:42:05 +02:00
access_fs_ro & = ruleset_attr . handled_access_fs ;
access_fs_rw & = ruleset_attr . handled_access_fs ;
2022-05-06 18:10:59 +02:00
ruleset_fd =
landlock_create_ruleset ( & ruleset_attr , sizeof ( ruleset_attr ) , 0 ) ;
if ( ruleset_fd < 0 ) {
perror ( " Failed to create a ruleset " ) ;
return 1 ;
}
if ( populate_ruleset ( ENV_FS_RO_NAME , ruleset_fd , access_fs_ro ) ) {
2021-04-22 17:41:21 +02:00
goto err_close_ruleset ;
}
2022-05-06 18:10:59 +02:00
if ( populate_ruleset ( ENV_FS_RW_NAME , ruleset_fd , access_fs_rw ) ) {
2021-04-22 17:41:21 +02:00
goto err_close_ruleset ;
}
if ( prctl ( PR_SET_NO_NEW_PRIVS , 1 , 0 , 0 , 0 ) ) {
perror ( " Failed to restrict privileges " ) ;
goto err_close_ruleset ;
}
if ( landlock_restrict_self ( ruleset_fd , 0 ) ) {
perror ( " Failed to enforce ruleset " ) ;
goto err_close_ruleset ;
}
close ( ruleset_fd ) ;
cmd_path = argv [ 1 ] ;
cmd_argv = argv + 1 ;
execvpe ( cmd_path , cmd_argv , envp ) ;
fprintf ( stderr , " Failed to execute \" %s \" : %s \n " , cmd_path ,
2022-05-06 18:05:13 +02:00
strerror ( errno ) ) ;
2021-04-22 17:41:21 +02:00
fprintf ( stderr , " Hint: access to the binary, the interpreter or "
" shared libraries may be denied. \n " ) ;
return 1 ;
err_close_ruleset :
close ( ruleset_fd ) ;
return 1 ;
}