-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3df0c00
commit 5909883
Showing
20 changed files
with
1,299 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
cmake_minimum_required(VERSION 3.12) | ||
project(IA2Runtime) | ||
|
||
add_executable(read-pkru | ||
read_pkru_demo.c | ||
get_inferior_pkru.c | ||
) | ||
|
||
add_executable(track-memory-map | ||
memory_map.c | ||
track_memory_map_demo.c | ||
track_memory_map.c | ||
mmap_event.c | ||
get_inferior_pkru.c | ||
) | ||
|
||
add_executable(seccomp-filter | ||
seccomp_filter.c | ||
) | ||
|
||
add_executable(landlock | ||
landlock.c | ||
strv.c | ||
forbid_paths.c | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
#define _GNU_SOURCE | ||
#include <ftw.h> | ||
#include <linux/landlock.h> | ||
#include <linux/prctl.h> | ||
|
||
#include <errno.h> | ||
#include <fcntl.h> | ||
#include <stdbool.h> | ||
#include <stdio.h> | ||
#include <string.h> | ||
|
||
#include "forbid_paths.h" | ||
#include "landlock.h" | ||
#include "strv.h" | ||
|
||
static int allow_path(const char *path, const int ruleset_fd, bool shallow) { | ||
struct landlock_path_beneath_attr path_beneath = { | ||
.parent_fd = -1, | ||
}; | ||
|
||
struct stat statbuf; | ||
|
||
path_beneath.parent_fd = open(path, O_PATH | O_CLOEXEC); | ||
if (path_beneath.parent_fd < 0) { | ||
fprintf(stderr, "Failed to open \"%s\": %s\n", path, strerror(errno)); | ||
return -1; | ||
} | ||
if (fstat(path_beneath.parent_fd, &statbuf)) { | ||
close(path_beneath.parent_fd); | ||
return -1; | ||
} | ||
path_beneath.allowed_access = | ||
ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE; | ||
|
||
/* limit non-directory files to access flags relevant for regular files */ | ||
if (!S_ISDIR(statbuf.st_mode)) { | ||
path_beneath.allowed_access &= ACCESS_FILE; | ||
} else { | ||
/* if we only shallowly allow this directory, only allow READ_DIR */ | ||
if (shallow) | ||
path_beneath.allowed_access = LANDLOCK_ACCESS_FS_READ_DIR; | ||
} | ||
|
||
/* add rule */ | ||
if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, &path_beneath, | ||
0)) { | ||
fprintf(stderr, "Failed to update the ruleset with \"%s\": %s\n", path, | ||
strerror(errno)); | ||
close(path_beneath.parent_fd); | ||
return -1; | ||
} | ||
close(path_beneath.parent_fd); | ||
|
||
return 0; | ||
} | ||
|
||
/* does path have the given prefix as a prefix of its path segments? */ | ||
static bool path_has_segment_prefix(const char *path, const char *prefix) { | ||
if (!strcmp(prefix, "/")) { | ||
return true; | ||
} | ||
|
||
while (*path == *prefix && *path && *prefix) { | ||
path++; | ||
prefix++; | ||
} | ||
|
||
if (*prefix == '\0' && (*path == '/' || *path == '\0')) { | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
/* | ||
Forbid access to the given paths, a null-terminated string vector. | ||
Because landlock is an allowlist-based system, we need to allow everything other | ||
than the specified paths. This is done by walking the directory tree downward | ||
from / toward each path. At each level, directories that do not (transitively) | ||
contain any forbidden files are recursively allowed, and directories that do are | ||
shallowly allowed. | ||
When the forbidden path is reached, its siblings are allowed by the same process | ||
(which thereby avoids allowing other forbidden files). | ||
*/ | ||
|
||
static struct forbid_ctx { | ||
const char **forbidden_paths; | ||
int ruleset_fd; | ||
int error; | ||
} ctx; | ||
|
||
/* At each level, directories that do not (transitively) contain any forbidden | ||
files are recursively allowed, and directories that do are shallowly allowed. */ | ||
static int forbid_deep_or_shallow(const char *fpath, const struct stat *sb, | ||
int typeflag, struct FTW *ftwbuf) { | ||
const char *forbidden_path; | ||
|
||
bool contains_forbidden_path = false; | ||
bool is_forbidden_path = false; | ||
/* is any forbidden path a descendant of this dir? */ | ||
for (int i = 0; (forbidden_path = ctx.forbidden_paths[i]); i++) { | ||
/* is forbidden_path a descendant of this dir? */ | ||
bool prefix_matches = path_has_segment_prefix(forbidden_path, fpath); | ||
if (prefix_matches) { | ||
contains_forbidden_path = true; | ||
} | ||
is_forbidden_path |= !strcmp(forbidden_path, fpath); | ||
if (is_forbidden_path) { | ||
break; | ||
} | ||
} | ||
|
||
/* if this is a forbidden path, do not allow it or children, just move on */ | ||
if (is_forbidden_path) { | ||
return FTW_SKIP_SUBTREE; | ||
} | ||
|
||
/* do not allow or continue beneath symbolic links */ | ||
if (typeflag == FTW_SL) { | ||
return FTW_SKIP_SUBTREE; | ||
} | ||
|
||
/* if contains forbidden path, allow shallowly and process children */ | ||
if (contains_forbidden_path) { | ||
int ret = allow_path(fpath, ctx.ruleset_fd, true); | ||
if (ret < 0) { | ||
ctx.error = ret; | ||
return FTW_STOP; | ||
} | ||
|
||
return FTW_CONTINUE; | ||
} | ||
|
||
/* allow whole dir or file */ | ||
int ret = allow_path(fpath, ctx.ruleset_fd, false); | ||
if (ret < 0) { | ||
ctx.error = ret; | ||
return FTW_STOP; | ||
} | ||
|
||
/* do not inspect individual children if this is an allowed dir */ | ||
if (typeflag == FTW_D || typeflag == FTW_DNR) { | ||
return FTW_SKIP_SUBTREE; | ||
} else { | ||
return FTW_CONTINUE; | ||
} | ||
} | ||
|
||
/* one-argument wrapper for realpath */ | ||
static char *realpath_alloc(const char *path) { return realpath(path, NULL); } | ||
|
||
int forbid_paths(const char **paths, const int ruleset_fd) { | ||
/* normalize away symbolic links in the forbidden paths. if multiple routes to | ||
* a file were allowed to exist (as permitted by links), we would allow the | ||
* one not mentioned by name as a forbidden path, contrary to our intent. */ | ||
char **real_paths = strvmap(paths, realpath_alloc); | ||
ctx.forbidden_paths = (const char **)real_paths; | ||
ctx.ruleset_fd = ruleset_fd; | ||
ctx.error = 0; | ||
nftw("/", forbid_deep_or_shallow, 512, FTW_PHYS | FTW_ACTIONRETVAL); | ||
strvfree(real_paths); | ||
return ctx.error; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
#pragma once | ||
|
||
int forbid_paths(const char **paths, const int ruleset_fd); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
#include <linux/elf.h> | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <string.h> | ||
#include <sys/ptrace.h> | ||
#include <sys/uio.h> | ||
|
||
#include "get_inferior_pkru.h" | ||
|
||
/* this is largely copped from gdb and pared down to just what we need. it would | ||
* be much more complex if we had to deal with the compacted xsave area. */ | ||
|
||
#define X86_XSTATE_PKRU_SIZE 2696 | ||
#define X86_XSTATE_MAX_SIZE 2696 | ||
|
||
/* offset to the location of the PKRU register data structure used by the | ||
* "xsave" instruction */ | ||
static int xsave_pkeys_offset = | ||
2688 + 0 * 8; /* %pkru (64 bits in XSTATE, 32-bit actually used by | ||
instructions and applications). */ | ||
|
||
bool get_inferior_pkru(pid_t pid, uint32_t *pkru_out) { | ||
char xstateregs[X86_XSTATE_MAX_SIZE]; | ||
struct iovec iov; | ||
|
||
/* Pre-4.14 kernels have a bug (fixed by commit 0852b374173b | ||
"x86/fpu: Add FPU state copying quirk to handle XRSTOR failure on | ||
Intel Skylake CPUs") that sometimes causes the mxcsr location in | ||
xstateregs not to be copied by PTRACE_GETREGSET. Make sure that | ||
the location is at least initialized with a defined value. */ | ||
memset(xstateregs, 0, sizeof(xstateregs)); | ||
iov.iov_base = xstateregs; | ||
iov.iov_len = sizeof(xstateregs); | ||
if (ptrace(PTRACE_GETREGSET, pid, (unsigned int)NT_X86_XSTATE, (long)&iov) < | ||
0) { | ||
perror("could not read xstate registers"); | ||
return false; | ||
} | ||
|
||
memcpy(pkru_out, &xstateregs[xsave_pkeys_offset], sizeof(*pkru_out)); | ||
return true; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
#include <stdbool.h> | ||
#include <stdint.h> | ||
#include <sys/types.h> | ||
|
||
bool get_inferior_pkru(pid_t pid, uint32_t *pkru_out); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
#include "landlock.h" | ||
#include "forbid_paths.h" | ||
#include "strv.h" | ||
|
||
#include <errno.h> | ||
#include <fcntl.h> | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <string.h> | ||
|
||
int main(const int argc, char *const argv[], char *const *const envp) { | ||
const char *cmd_path; | ||
char *const *cmd_argv; | ||
int ruleset_fd, abi_ver; | ||
__u64 access_fs_rw = ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE; | ||
struct landlock_ruleset_attr ruleset_attr = { | ||
.handled_access_fs = access_fs_rw, | ||
}; | ||
|
||
if (argc < 2) { | ||
fprintf(stderr, "usage: DENY_PATH=... %s <command>\n\n", basename(argv[0])); | ||
fprintf(stderr, "built against landlock ABI version <= %d\n", | ||
LANDLOCK_ABI_LAST); | ||
return 1; | ||
} | ||
|
||
abi_ver = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION); | ||
if (abi_ver < 0) { | ||
const int err = errno; | ||
|
||
perror("Failed to check Landlock compatibility"); | ||
switch (err) { | ||
case ENOSYS: | ||
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"); | ||
break; | ||
case EOPNOTSUPP: | ||
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"); | ||
break; | ||
} | ||
return 1; | ||
} | ||
|
||
switch (abi_ver) { | ||
case 1: | ||
/* | ||
* Removes LANDLOCK_ACCESS_FS_REFER for ABI < 2 | ||
* | ||
* Note: The "refer" operations (file renaming and linking | ||
* across different directories) are always forbidden when using | ||
* Landlock with ABI 1. | ||
* | ||
* If only ABI 1 is available, this sandboxer knowingly forbids | ||
* refer operations. | ||
* | ||
* If a program *needs* to do refer operations after enabling | ||
* Landlock, it can not use Landlock at ABI level 1. To be | ||
* compatible with different kernel versions, such programs | ||
* should then fall back to not restrict themselves at all if | ||
* the running kernel only supports ABI 1. | ||
*/ | ||
ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_REFER_OR_0; | ||
__attribute__((fallthrough)); | ||
case 2: | ||
/* Removes LANDLOCK_ACCESS_FS_TRUNCATE for ABI < 3 */ | ||
ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_TRUNCATE_OR_0; | ||
|
||
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_ver); | ||
__attribute__((fallthrough)); | ||
case LANDLOCK_ABI_LAST: | ||
break; | ||
default: | ||
fprintf( | ||
stderr, | ||
"rebuild sandboxer to use features from ABI version %d instead of %d\n", | ||
abi_ver, LANDLOCK_ABI_LAST); | ||
} | ||
access_fs_rw &= ruleset_attr.handled_access_fs; | ||
|
||
ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); | ||
if (ruleset_fd < 0) { | ||
perror("Failed to create ruleset"); | ||
return 1; | ||
} | ||
const char *path = getenv("DENY_PATH"); | ||
const char *paths[] = {path, NULL}; | ||
if (forbid_paths(paths, ruleset_fd) < 0) { | ||
fprintf(stderr, "Failed to set up path allowlist\n"); | ||
return 1; | ||
} | ||
|
||
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); | ||
perror("execvpe"); | ||
return 1; | ||
|
||
err_close_ruleset: | ||
close(ruleset_fd); | ||
return 1; | ||
} |
Oops, something went wrong.