From 8b07b18a44f7e0ebdb65b791d79d588dd90b70b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Renaud=20M=C3=A9trich?= Date: Tue, 5 Oct 2021 08:36:22 +0200 Subject: [PATCH 169/174] Implement displaying of expected context upon mismatch New option to --secontext=... (also available as -e secontext=...) - mismatch: print expected context on mismatch When using 'mismatch', an additional check is made on the context by reading the context database and comparing the output after stripping the unwanted part (e.g. stripping nothing in 'full' mode, keeping the type only in default mode): - if it differs, prints the expected context after printing '!!' - if not, don't print anything Example with /home/rmetrich/GIT/strace/autom4te.cache/output.3 file: ---- $ matchpathcon /home/rmetrich/GIT/strace/autom4te.cache/output.3 /home/rmetrich/GIT/strace/autom4te.cache/output.3 unconfined_u:object_r:user_home_t:s0 $ ls -Z /home/rmetrich/GIT/strace/autom4te.cache/output.3 system_u:object_r:user_home_t:s0 /home/rmetrich/GIT/strace/autom4te.cache/output.3 ---- From above, we see the user part differs ('unconfined_u' vs 'system_u') Output in '!full' mode (no diff found on type): ---- $ strace --secontext=mismatch -e statx stat /home/rmetrich/GIT/strace/autom4te.cache/output.3 ... statx(AT_FDCWD, "/home/rmetrich/GIT/strace/autom4te.cache/output.3" [user_home_t], ... ---- Output in 'full' mode (diff found on user): ---- ... statx(AT_FDCWD, "/home/rmetrich/GIT/strace/autom4te.cache/output.3" [system_u:object_r:user_home_t:s0!!unconfined_u:object_r:user_home_t:s0], ... ---- * NEWS: Mention this change. * doc/strace.1.in: Document it. * m4/st_selinux.m4 (st_SELINUX): Check for selabel_open and selabel_lookup. * src/filter_qualify.c [ENABLE_SECONTEXT]: Include "secontext.h". [ENABLE_SECONTEXT] (secontext_set): New variable. [ENABLE_SECONTEXT] (secontextstr_to_uint, qualify_secontext): New functions. (qual_options) [ENABLE_SECONTEXT]: Add "secontext". * src/secontext.c: Include , , , "largefile_wrappers.h", "number_set.h", and "xmalloc.h". (selinux_context, selinux_context_full): Remove. (getcontext): Use is_number_in_set instead of selinux_context_full. (selinux_getpidcon): Use is_number_in_set instead of selinux_context. (get_expected_filecontext): New function. (selinux_getfdcon, selinux_getfilecon): Use it to print context mismatch if SECONTEXT_MISMATCH is set in secontext_set. * src/secontext.h (selinux_context, selinux_context_full): Remove. (secontext_bits): New enum. (secontext_set, qualify_secontext, selinux_set_format): New declarations. * src/strace.c (SECONTEXT_E_QUAL): New macro. (usage): Use it, describe --secontext. (init) [ENABLE_SECONTEXT]: Call qualify_secontext, rename GETOPT_SECONTEXT to GETOPT_QUAL_SECONTEXT, use is_number_in_set instead of selinux_context. (init) [ENABLE_SECONTEXT] (secontext_qual): New variable. (init) [ENABLE_SECONTEXT] : Use it. * tests/.gitignore: Add *--secontext_full_mismatch, *--secontext_full_mismatch.c, *--secontext_mismatch, and *--secontext_mismatch.c. * tests/gen_secontext.sh: Generate *--secontext_full_mismatch.c and *--secontext_mismatch.c. * tests/gen_tests.in (access--secontext_full_mismatch, access--secontext_mismatch, chmod--secontext_full_mismatch, chmod--secontext_mismatch, execve--secontext_full_mismatch, execve--secontext_mismatch, execveat--secontext_full_mismatch, execveat--secontext_mismatch, faccessat--secontext_full_mismatch, faccessat--secontext_mismatch, faccessat-y--secontext_full_mismatch, faccessat-y--secontext_mismatch, fanotify_mark--secontext_full_mismatch, fanotify_mark--secontext_mismatch, fchmod--secontext_full_mismatch, fchmod--secontext_mismatch, fchmod-y--secontext_full_mismatch, fchmod-y--secontext_mismatch, fchmodat--secontext_full_mismatch, fchmodat--secontext_mismatch, fchownat--secontext_full_mismatch, fchownat--secontext_mismatch, file_handle--secontext_full_mismatch, file_handle--secontext_mismatch, linkat--secontext_full_mismatch, linkat--secontext_mismatch, open--secontext_full_mismatch, open--secontext_mismatch, openat--secontext_full_mismatch, openat--secontext_mismatch): New tests. * tests/linkat.c: Include . (main) [PRINT_SECONTEXT_MISMATCH]: Check context mismatch. * tests/options-syntax.test: Check --secontext and -e secontext syntax. * tests/secontext.h (secontext_field): New enum. (secontext_full_file, secontext_short_file): Add "mismatch" argument. (update_secontext_type): Rename to update_secontext_field, add "field" argument. (SECONTEXT_FILE): Conditionalize "mismatch" argument passed to secontext_full_file and secontext_short_file on PRINT_SECONTEXT_MISMATCH. * tests/secontext.c: Include and . (get_type_from_context, raw_expected_secontext_full_file, raw_expected_secontext_short_file): New functions. (raw_secontext_short_file, raw_secontext_short_pid): Use get_type_from_context. (secontext_full_file): Add "mismatch" argument, use raw_expected_secontext_full_file if mismatch is enabled. (secontext_short_file): Add "mismatch" argument, use raw_expected_secontext_short_file if mismatch is enabled. (update_secontext_type): Rename to update_secontext_field, add "field" argument. Co-authored-by: Dmitry V. Levin Conflicts: NEWS doc/strace.1.in src/filter_qualify.c src/strace.c --- NEWS | 2 + doc/strace.1.in | 33 +++++++-- m4/st_selinux.m4 | 2 +- src/filter_qualify.c | 29 ++++++++ src/secontext.c | 114 ++++++++++++++++++++++++++---- src/secontext.h | 15 +++- src/strace.c | 49 ++++++++----- tests/.gitignore | 4 ++ tests/gen_secontext.sh | 12 +++- tests/gen_tests.in | 34 ++++++++- tests/linkat.c | 46 +++++++++++- tests/options-syntax.test | 14 +++- tests/secontext.c | 176 ++++++++++++++++++++++++++++++++-------------- tests/secontext.h | 28 ++++++-- 14 files changed, 453 insertions(+), 105 deletions(-) diff --git a/NEWS b/NEWS index 969ed11..f7542ea 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,8 @@ Noteworthy changes in release 5.13 (2021-07-18) =============================================== * Improvements + * Implemented --secontext=mismatch option to find mismatches in SELinux + contexts. * Print netlink data in a more structured way. * Implemented decoding of NT_PRSTATUS and NT_FPREGSET regsets of PTRACE_GETREGSET and PTRACE_SETREGSET requests. diff --git a/doc/strace.1.in b/doc/strace.1.in index 003e9e5..439243b 100644 --- a/doc/strace.1.in +++ b/doc/strace.1.in @@ -53,7 +53,7 @@ strace \- trace system calls and signals .OM \-P path .OM \-p pid .OP \-\-seccomp\-bpf -.if '@ENABLE_SECONTEXT_FALSE@'#' .OP \-\-secontext\fR[=full] +.if '@ENABLE_SECONTEXT_FALSE@'#' .OP \-\-secontext\fR[=\fIformat\fR] .BR "" { .OR \-p pid .BR "" | @@ -259,6 +259,7 @@ is one of .BR inject , .BR status , .BR quiet " (or " silent " or " silence " or " q ), +.if '@ENABLE_SECONTEXT_FALSE@'#' .BR secontext , .BR decode\-fds " (or " decode\-fd ), or .BR kvm , @@ -1086,13 +1087,33 @@ and PIDs associated with pidfd file descriptors. If strace and tracee are in different PID namespaces, print PIDs in strace's namespace, too. .if '@ENABLE_SECONTEXT_FALSE@'#' .TP -.if '@ENABLE_SECONTEXT_FALSE@'#' .BR \-\-secontext "[=full]" +.if '@ENABLE_SECONTEXT_FALSE@'#' .BR \-\-secontext\fR[=\fIformat\fR] +.if '@ENABLE_SECONTEXT_FALSE@'#' .TQ +.if '@ENABLE_SECONTEXT_FALSE@'#' .BR \-e\ secontext\fR=\fIformat\fR .if '@ENABLE_SECONTEXT_FALSE@'#' When SELinux is available and is not disabled, .if '@ENABLE_SECONTEXT_FALSE@'#' print in square brackets SELinux contexts of -.if '@ENABLE_SECONTEXT_FALSE@'#' processes, files, and descriptors. When -.if '@ENABLE_SECONTEXT_FALSE@'#' .B full -.if '@ENABLE_SECONTEXT_FALSE@'#' is specified, print the complete context (user, -.if '@ENABLE_SECONTEXT_FALSE@'#' role, type and category) instead of just the type. +.if '@ENABLE_SECONTEXT_FALSE@'#' processes, files, and descriptors. The +.if '@ENABLE_SECONTEXT_FALSE@'#' .I format +.if '@ENABLE_SECONTEXT_FALSE@'#' argument is a comma-separated list of items +.if '@ENABLE_SECONTEXT_FALSE@'#' being one of the following: +.if '@ENABLE_SECONTEXT_FALSE@'#' .RS +.if '@ENABLE_SECONTEXT_FALSE@'#' .TP 18 +.if '@ENABLE_SECONTEXT_FALSE@'#' .BR full +.if '@ENABLE_SECONTEXT_FALSE@'#' Print the full context (user, role, type level +.if '@ENABLE_SECONTEXT_FALSE@'#' and category). +.if '@ENABLE_SECONTEXT_FALSE@'#' .TQ +.if '@ENABLE_SECONTEXT_FALSE@'#' .BR mismatch +.if '@ENABLE_SECONTEXT_FALSE@'#' Also print the context recorded by the SELinux +.if '@ENABLE_SECONTEXT_FALSE@'#' database in case the current context differs. +.if '@ENABLE_SECONTEXT_FALSE@'#' The latter is printed after two exclamation marks (!!). +.if '@ENABLE_SECONTEXT_FALSE@'#' .RE +.if '@ENABLE_SECONTEXT_FALSE@'#' .IP +.if '@ENABLE_SECONTEXT_FALSE@'#' The default value for +.if '@ENABLE_SECONTEXT_FALSE@'#' .BR \-\-secontext +.if '@ENABLE_SECONTEXT_FALSE@'#' is +.if '@ENABLE_SECONTEXT_FALSE@'#' .BR !full,mismatch +.if '@ENABLE_SECONTEXT_FALSE@'#' which prints only the type instead of full context +.if '@ENABLE_SECONTEXT_FALSE@'#' and doesn't check for context mismatches. .SS Statistics .TP 12 .B \-c diff --git a/m4/st_selinux.m4 b/m4/st_selinux.m4 index 7b24eba..60e23a9 100644 --- a/m4/st_selinux.m4 +++ b/m4/st_selinux.m4 @@ -35,7 +35,7 @@ AS_IF([test "x$with_libselinux" != xno], [saved_LDFLAGS="$LDFLAGS" LDFLAGS="$LDFLAGS $libselinux_LDFLAGS" missing= - for func in getpidcon getfilecon; do + for func in getpidcon getfilecon selabel_open selabel_lookup; do AC_CHECK_LIB([selinux], [$func], [:], [missing="$missing $func"]) done diff --git a/src/filter_qualify.c b/src/filter_qualify.c index df05496..a5b4fe7 100644 --- a/src/filter_qualify.c +++ b/src/filter_qualify.c @@ -14,6 +14,9 @@ #include "poke.h" #include "retval.h" #include "static_assert.h" +#ifdef ENABLE_SECONTEXT +# include "secontext.h" +#endif struct number_set *read_set; struct number_set *write_set; @@ -591,6 +594,29 @@ qualify_kvm(const char *const str) } } +#ifdef ENABLE_SECONTEXT +struct number_set *secontext_set; + +static int +secontextstr_to_uint(const char *s) +{ + static const struct xlat_data secontext_strs[] = { + { SECONTEXT_FULL, "full" }, + { SECONTEXT_MISMATCH, "mismatch" }, + }; + + return (int) find_arg_val(s, secontext_strs, -1ULL, -1ULL); +} + +void +qualify_secontext(const char *const str) +{ + if (!secontext_set) + secontext_set = alloc_number_set_array(1); + qualify_tokens(str, secontext_set, secontextstr_to_uint, "secontext"); +} +#endif + static const struct qual_options { const char *name; void (*qualify)(const char *); @@ -622,6 +648,9 @@ static const struct qual_options { { "kvm", qualify_kvm }, { "decode-fd", qualify_decode_fd }, { "decode-fds", qualify_decode_fd }, +#ifdef ENABLE_SECONTEXT + { "secontext", qualify_secontext }, +#endif }; void diff --git a/src/secontext.c b/src/secontext.c index ccf9b34..9a91386 100644 --- a/src/secontext.c +++ b/src/secontext.c @@ -10,14 +10,17 @@ #include #include #include +#include +#include #include +#include +#include "largefile_wrappers.h" +#include "number_set.h" #include "secontext.h" +#include "xmalloc.h" #include "xstring.h" -bool selinux_context = false; -bool selinux_context_full = false; - static int getcontext(int rc, char **secontext, char **result) { @@ -25,7 +28,7 @@ getcontext(int rc, char **secontext, char **result) return rc; *result = NULL; - if (!selinux_context_full) { + if (!is_number_in_set(SECONTEXT_FULL, secontext_set)) { char *saveptr = NULL; char *secontext_copy = xstrdup(*secontext); const char *token; @@ -59,6 +62,36 @@ getcontext(int rc, char **secontext, char **result) freecon(*secontext); return 0; } + +static int +get_expected_filecontext(const char *path, char **result) +{ + static struct selabel_handle *hdl; + + if (!hdl) { + static bool disabled; + if (disabled) + return -1; + + hdl = selabel_open(SELABEL_CTX_FILE, NULL, 0); + if (!hdl) { + perror_msg("could not open SELinux database, disabling " + "context mismatch checking"); + disabled = true; + return -1; + } + } + + strace_stat_t stb; + if (stat_file(path, &stb) < 0) { + return -1; + } + + char *secontext; + return getcontext(selabel_lookup(hdl, &secontext, path, stb.st_mode), + &secontext, result); +} + /* * Retrieves the SELinux context of the given PID (extracted from the tcb). * Memory must be freed. @@ -67,7 +100,7 @@ getcontext(int rc, char **secontext, char **result) int selinux_getpidcon(struct tcb *tcp, char **result) { - if (!selinux_context) + if (number_set_array_is_empty(secontext_set, 0)) return -1; int proc_pid = 0; @@ -87,7 +120,7 @@ selinux_getpidcon(struct tcb *tcp, char **result) int selinux_getfdcon(pid_t pid, int fd, char **result) { - if (!selinux_context || pid <= 0 || fd < 0) + if (number_set_array_is_empty(secontext_set, 0) || pid <= 0 || fd < 0) return -1; int proc_pid = 0; @@ -99,7 +132,33 @@ selinux_getfdcon(pid_t pid, int fd, char **result) xsprintf(linkpath, "/proc/%u/fd/%u", proc_pid, fd); char *secontext; - return getcontext(getfilecon(linkpath, &secontext), &secontext, result); + int rc = getcontext(getfilecon(linkpath, &secontext), &secontext, result); + if (rc < 0 || !is_number_in_set(SECONTEXT_MISMATCH, secontext_set)) + return rc; + + /* + * We need to resolve the path, because selabel_lookup() doesn't + * resolve anything. Using readlink() is sufficient here. + */ + + char buf[PATH_MAX]; + ssize_t n = readlink(linkpath, buf, sizeof(buf)); + if ((size_t) n >= sizeof(buf)) + return 0; + buf[n] = '\0'; + + char *expected; + if (get_expected_filecontext(buf, &expected) < 0) + return 0; + if (strcmp(expected, *result) == 0) { + free(expected); + return 0; + } + char *final_result = xasprintf("%s!!%s", *result, expected); + free(expected); + free(*result); + *result = final_result; + return 0; } /* @@ -110,7 +169,7 @@ selinux_getfdcon(pid_t pid, int fd, char **result) int selinux_getfilecon(struct tcb *tcp, const char *path, char **result) { - if (!selinux_context) + if (number_set_array_is_empty(secontext_set, 0)) return -1; int proc_pid = 0; @@ -118,22 +177,49 @@ selinux_getfilecon(struct tcb *tcp, const char *path, char **result) if (!proc_pid) return -1; - int ret = -1; + int rc = -1; char fname[PATH_MAX]; if (path[0] == '/') - ret = snprintf(fname, sizeof(fname), "/proc/%u/root%s", + rc = snprintf(fname, sizeof(fname), "/proc/%u/root%s", proc_pid, path); else if (tcp->last_dirfd == AT_FDCWD) - ret = snprintf(fname, sizeof(fname), "/proc/%u/cwd/%s", + rc = snprintf(fname, sizeof(fname), "/proc/%u/cwd/%s", proc_pid, path); else if (tcp->last_dirfd >= 0 ) - ret = snprintf(fname, sizeof(fname), "/proc/%u/fd/%u/%s", + rc = snprintf(fname, sizeof(fname), "/proc/%u/fd/%u/%s", proc_pid, tcp->last_dirfd, path); - if ((unsigned int) ret >= sizeof(fname)) + if ((unsigned int) rc >= sizeof(fname)) return -1; char *secontext; - return getcontext(getfilecon(fname, &secontext), &secontext, result); + rc = getcontext(getfilecon(fname, &secontext), &secontext, result); + if (rc < 0 || !is_number_in_set(SECONTEXT_MISMATCH, secontext_set)) + return rc; + + /* + * We need to fully resolve the path, because selabel_lookup() doesn't + * resolve anything. Using realpath() is the only solution here to make + * sure the path is canonicalized. + */ + + char *resolved = realpath(fname, NULL); + if (!resolved) + return 0; + + char *expected; + rc = get_expected_filecontext(resolved, &expected); + free(resolved); + if (rc < 0) + return 0; + if (strcmp(expected, *result) == 0) { + free(expected); + return 0; + } + char *final_result = xasprintf("%s!!%s", *result, expected); + free(expected); + free(*result); + *result = final_result; + return 0; } diff --git a/src/secontext.h b/src/secontext.h index 1ed88c7..39222d0 100644 --- a/src/secontext.h +++ b/src/secontext.h @@ -11,11 +11,22 @@ # include "defs.h" -extern bool selinux_context; -extern bool selinux_context_full; +void qualify_secontext(const char *const str); + +enum secontext_bits { + /* Display full context instead of type only */ + SECONTEXT_FULL, + /* Check for context mismatch */ + SECONTEXT_MISMATCH, + + NUMBER_OF_SECONTEXT_BITS +}; + +extern struct number_set *secontext_set; int selinux_getfdcon(pid_t pid, int fd, char **context); int selinux_getfilecon(struct tcb *tcp, const char *path, char **context); int selinux_getpidcon(struct tcb *tcp, char **context); +void selinux_set_format(const char *optarg); #endif /* !STRACE_SECONTEXT_H */ diff --git a/src/strace.c b/src/strace.c index fb42fe9..0a18478 100644 --- a/src/strace.c +++ b/src/strace.c @@ -264,9 +264,11 @@ usage(void) # define K_OPT "" #endif #ifdef ENABLE_SECONTEXT -# define SECONTEXT_OPT "[--secontext[=full]]\n" +# define SECONTEXT_OPT " [--secontext[=FORMAT]]\n" +# define SECONTEXT_E_QUAL ", secontext" #else # define SECONTEXT_OPT "" +# define SECONTEXT_E_QUAL "" #endif printf("\ @@ -282,7 +284,7 @@ Usage: strace [-ACdffhi" K_OPT "qqrtttTvVwxxyyzZ] [-I N] [-b execve] [-e EXPR].. General:\n\ -e EXPR a qualifying expression: OPTION=[!]all or OPTION=[!]VAL1[,VAL2]...\n\ options: trace, abbrev, verbose, raw, signal, read, write, fault,\n\ - inject, status, quiet, kvm, decode-fds\n\ + inject, status, quiet, kvm, decode-fds" SECONTEXT_E_QUAL "\n\ \n\ Startup:\n\ -E VAR=VAL, --env=VAR=VAL\n\ @@ -358,6 +360,19 @@ Output format:\n\ path (file path),\n\ pidfd (associated PID for pidfds),\n\ socket (protocol-specific information for socket descriptors)\n\ +" +#ifdef ENABLE_SECONTEXT +"\ + -e secontext=FORMAT, --secontext[=FORMAT]\n\ + print SELinux contexts in square brackets\n\ + formats: comma-separated list of all, full, mismatch, none\n\ + all: equivalent to full,mismatch\n\ + full: print the full context instead of the type only\n\ + mismatch: print expected context when actual is not matching\n\ + none: equivalent to not specifying the option at all\n\ +" +#endif +"\ -i, --instruction-pointer\n\ print instruction pointer at time of syscall\n\ " @@ -1991,6 +2006,9 @@ init(int argc, char *argv[]) static const char tflag_str[] = "format:time"; static const char ttflag_str[] = "precision:us,format:time"; static const char tttflag_str[] = "format:unix,precision:us"; +#ifdef ENABLE_SECONTEXT + static const char secontext_qual[] = "!full,mismatch"; +#endif int c, i; int optF = 0, zflags = 0; @@ -2054,6 +2072,9 @@ init(int argc, char *argv[]) qualify_quiet("none"); qualify_decode_fd("none"); qualify_signals("all"); +#ifdef ENABLE_SECONTEXT + qualify_secontext("none"); +#endif static const char optstring[] = "+a:Ab:cCdDe:E:fFhiI:kno:O:p:P:qrs:S:tTu:U:vVwxX:yzZ"; @@ -2066,9 +2087,6 @@ init(int argc, char *argv[]) GETOPT_OUTPUT_SEPARATELY, GETOPT_TS, GETOPT_PIDNS_TRANSLATION, -#ifdef ENABLE_SECONTEXT - GETOPT_SECONTEXT, -#endif GETOPT_QUAL_TRACE, GETOPT_QUAL_ABBREV, @@ -2083,6 +2101,9 @@ init(int argc, char *argv[]) GETOPT_QUAL_KVM, GETOPT_QUAL_QUIET, GETOPT_QUAL_DECODE_FD, +#ifdef ENABLE_SECONTEXT + GETOPT_QUAL_SECONTEXT, +#endif }; static const struct option longopts[] = { { "columns", required_argument, 0, 'a' }, @@ -2125,9 +2146,6 @@ init(int argc, char *argv[]) { "failed-only", no_argument, 0, 'Z' }, { "failing-only", no_argument, 0, 'Z' }, { "seccomp-bpf", no_argument, 0, GETOPT_SECCOMP }, -#ifdef ENABLE_SECONTEXT - { "secontext", optional_argument, 0, GETOPT_SECONTEXT }, -#endif { "trace", required_argument, 0, GETOPT_QUAL_TRACE }, { "abbrev", required_argument, 0, GETOPT_QUAL_ABBREV }, @@ -2144,6 +2162,9 @@ init(int argc, char *argv[]) { "silent", optional_argument, 0, GETOPT_QUAL_QUIET }, { "silence", optional_argument, 0, GETOPT_QUAL_QUIET }, { "decode-fds", optional_argument, 0, GETOPT_QUAL_DECODE_FD }, +#ifdef ENABLE_SECONTEXT + { "secontext", optional_argument, 0, GETOPT_QUAL_SECONTEXT }, +#endif { 0, 0, 0, 0 } }; @@ -2357,14 +2378,8 @@ init(int argc, char *argv[]) seccomp_filtering = true; break; #ifdef ENABLE_SECONTEXT - case GETOPT_SECONTEXT: - selinux_context = true; - if (optarg) { - if (!strcmp(optarg, "full")) - selinux_context_full = true; - else - error_opt_arg(c, lopt, optarg); - } + case GETOPT_QUAL_SECONTEXT: + qualify_secontext(optarg ? optarg : secontext_qual); break; #endif case GETOPT_QUAL_TRACE: @@ -2550,7 +2565,7 @@ init(int argc, char *argv[]) error_msg("-y/--decode-fds has no effect " "with -c/--summary-only"); #ifdef ENABLE_SECONTEXT - if (selinux_context) + if (!number_set_array_is_empty(secontext_set, 0)) error_msg("--secontext has no effect with " "-c/--summary-only"); #endif diff --git a/tests/gen_tests.in b/tests/gen_tests.in index 8b4e2e9..71e2f17 100644 --- a/tests/gen_tests.in +++ b/tests/gen_tests.in @@ -12,6 +12,8 @@ accept4 -a37 access -a30 --trace-path=access_sample access--secontext -a30 --secontext --trace-path=access_sample -e trace=access access--secontext_full -a30 --secontext=full --trace-path=access_sample -e trace=access +access--secontext_full_mismatch -a30 --secontext=full,mismatch --trace-path=access_sample -e trace=access +access--secontext_mismatch -a30 --secontext=mismatch --trace-path=access_sample -e trace=access acct -a20 add_key -a30 -s12 adjtimex -a15 @@ -27,8 +29,10 @@ bpf-v -a20 -v -e trace=bpf btrfs +ioctl.test chdir -a10 chmod -a28 -chmod--secontext -a28 --secontext -e trace=chmod -chmod--secontext_full -a28 --secontext=full -e trace=chmod +chmod--secontext -a28 -e secontext=!full,mismatch -e trace=chmod +chmod--secontext_full -a28 -e secontext=full -e trace=chmod +chmod--secontext_full_mismatch -a28 --secontext=mismatch,full -e trace=chmod +chmod--secontext_mismatch -a28 --secontext=mismatch -e trace=chmod chown -a28 chown32 -a31 chroot -a13 @@ -84,16 +88,24 @@ epoll_wait -a26 erestartsys -a34 -e signal=none -e trace=recvfrom execve--secontext +execve.test --secontext execve--secontext_full +execve.test --secontext=full +execve--secontext_full_mismatch +execve.test --secontext=full,mismatch +execve--secontext_mismatch +execve.test --secontext=mismatch execveat execveat--secontext --secontext --trace=execveat execveat--secontext_full --secontext=full --trace=execveat +execveat--secontext_full_mismatch --secontext=full,mismatch --trace=execveat +execveat--secontext_mismatch --secontext=mismatch --trace=execveat execveat-v -v -e trace=execveat faccessat--secontext +faccessat.test -a24 --secontext faccessat--secontext_full +faccessat.test -a24 --secontext=full +faccessat--secontext_full_mismatch +faccessat.test -a24 --secontext=full,mismatch +faccessat--secontext_mismatch +faccessat.test -a24 --secontext=mismatch faccessat-P -a23 --trace=faccessat -P /dev/full faccessat-y +faccessat.test -a24 -y faccessat-y--secontext +faccessat.test -a24 -y --secontext faccessat-y--secontext_full +faccessat.test -a24 -y --secontext=full +faccessat-y--secontext_full_mismatch +faccessat.test -a24 -y --secontext=full,mismatch +faccessat-y--secontext_mismatch +faccessat.test -a24 -y --secontext=mismatch faccessat-yy +faccessat.test -a24 -yy faccessat2-P -a27 --trace=faccessat2 -P /dev/full faccessat2-y +faccessat2.test -a28 -y @@ -104,6 +116,8 @@ fanotify_init fanotify_mark -a32 fanotify_mark--secontext -a32 --secontext -e trace=fanotify_mark fanotify_mark--secontext_full -a32 --secontext=full -e trace=fanotify_mark +fanotify_mark--secontext_full_mismatch -a32 --secontext=full,mismatch -e trace=fanotify_mark +fanotify_mark--secontext_mismatch -a32 --secontext=mismatch -e trace=fanotify_mark fanotify_mark-Xabbrev -a32 -Xabbrev -e trace=fanotify_mark fanotify_mark-Xraw -a32 -Xraw -e trace=fanotify_mark fanotify_mark-Xverbose -a32 -Xverbose -e trace=fanotify_mark @@ -111,17 +125,25 @@ fchdir -a11 fchmod -a15 fchmod--secontext -a15 --secontext -e trace=fchmod fchmod--secontext_full -a15 --secontext=full -e trace=fchmod +fchmod--secontext_full_mismatch -a15 --secontext=full,mismatch -e trace=fchmod +fchmod--secontext_mismatch -a15 --secontext=mismatch -e trace=fchmod fchmod-y -y -e trace=fchmod fchmod-y--secontext -a15 -y --secontext -e trace=fchmod fchmod-y--secontext_full -a15 -y --secontext=full -e trace=fchmod +fchmod-y--secontext_full_mismatch -a15 -y --secontext=full,mismatch -e trace=fchmod +fchmod-y--secontext_mismatch -a15 -y --secontext=mismatch -e trace=fchmod fchmodat fchmodat--secontext --secontext -e trace=fchmodat fchmodat--secontext_full --secontext=full -e trace=fchmodat +fchmodat--secontext_full_mismatch --secontext=full,mismatch -e trace=fchmodat +fchmodat--secontext_mismatch --secontext=mismatch -e trace=fchmodat fchown -a16 fchown32 -a18 fchownat fchownat--secontext --secontext -e trace=fchownat fchownat--secontext_full --secontext=full -e trace=fchownat +fchownat--secontext_full_mismatch -e secontext=full,mismatch -e trace=fchownat +fchownat--secontext_mismatch -e secontext=mismatch -e trace=fchownat fcntl -a8 fcntl--pidns-translation test_pidns -a8 -e trace=fcntl fcntl64 -a8 @@ -130,6 +152,8 @@ fdatasync -a14 file_handle -e trace=name_to_handle_at,open_by_handle_at file_handle--secontext --secontext -e trace=name_to_handle_at,open_by_handle_at file_handle--secontext_full --secontext=full -e trace=name_to_handle_at,open_by_handle_at +file_handle--secontext_full_mismatch --secontext=full,mismatch -e trace=name_to_handle_at,open_by_handle_at +file_handle--secontext_mismatch --secontext=mismatch -e trace=name_to_handle_at,open_by_handle_at filter_seccomp . "${srcdir=.}/filter_seccomp.sh"; test_prog_set --seccomp-bpf -f filter_seccomp-flag ../$NAME finit_module -a25 @@ -383,6 +407,8 @@ link linkat linkat--secontext --secontext -e trace=linkat linkat--secontext_full --secontext=full -e trace=linkat +linkat--secontext_full_mismatch --secontext=full,mismatch -e trace=linkat +linkat--secontext_mismatch --secontext=mismatch -e trace=linkat lookup_dcookie -a27 lstat -a31 --no-abbrev --trace-path=stat.sample --trace-path=/dev/full lstat64 -a32 --no-abbrev --trace-path=stat.sample --trace-path=/dev/full @@ -526,11 +552,15 @@ oldstat -a32 -v -P stat.sample -P /dev/full open -a30 -P $NAME.sample open--secontext -a30 -P open.sample --secontext --trace=open open--secontext_full -a30 -P open.sample --secontext=full --trace=open +open--secontext_full_mismatch -a30 -P open.sample --secontext=full,mismatch --trace=open +open--secontext_mismatch -a30 -P open.sample --secontext=mismatch --trace=open open_tree -a30 -y open_tree-P -a30 --decode-fds -P /dev/full -e trace=open_tree openat -a36 -P $NAME.sample openat--secontext -a36 -P openat.sample -P $PWD/openat.sample --secontext -e trace=openat openat--secontext_full -a36 -P openat.sample -P $PWD/openat.sample --secontext=full -e trace=openat +openat--secontext_full_mismatch -a36 -P openat.sample -P $PWD/openat.sample --secontext=full,mismatch -e trace=openat +openat--secontext_mismatch -a36 -P openat.sample -P $PWD/openat.sample --secontext=mismatch -e trace=openat openat2 -a35 openat2-Xabbrev --trace=openat2 -a35 -Xabbrev openat2-Xraw --trace=openat2 -a32 -Xraw diff --git a/tests/linkat.c b/tests/linkat.c index 1d41d3d..1a869e3 100644 --- a/tests/linkat.c +++ b/tests/linkat.c @@ -15,6 +15,7 @@ #include #include #include +#include #include "secontext.h" #include "xmalloc.h" @@ -88,10 +89,42 @@ main(void) perror_msg_and_fail("close"); free(sample_1_secontext); - update_secontext_type(sample_1, "default_t"); + +#ifdef PRINT_SECONTEXT_MISMATCH + update_secontext_field(sample_1, SECONTEXT_USER, "system_u"); + sample_1_secontext = SECONTEXT_FILE(sample_1); + +# ifdef PRINT_SECONTEXT_FULL + /* The mismatch should be detected */ + if (*sample_1_secontext && strstr(sample_1_secontext, "!!") == NULL) + perror_msg_and_fail("Context mismatch not detected: %s", + sample_1_secontext); + if (*sample_1_secontext && strstr(sample_1_secontext, "system_u") == NULL) + perror_msg_and_fail("Context mismatch not detected: %s", + sample_1_secontext); +# else + /* The mismatch cannot be detected since it's on user part */ + if (*sample_1_secontext && strstr(sample_1_secontext, "!!") != NULL) + perror_msg_and_fail("Context mismatch detected: %s", + sample_1_secontext); +# endif + + free(sample_1_secontext); +#endif + + update_secontext_field(sample_1, SECONTEXT_TYPE, "default_t"); sample_1_secontext = SECONTEXT_FILE(sample_1); sample_2_secontext = sample_1_secontext; +#ifdef PRINT_SECONTEXT_MISMATCH + if (*sample_1_secontext && strstr(sample_1_secontext, "!!") == NULL) + perror_msg_and_fail("Context mismatch not detected: %s", + sample_1_secontext); + if (*sample_1_secontext && strstr(sample_1_secontext, "default_t") == NULL) + perror_msg_and_fail("Context mismatch not detected: %s", + sample_1_secontext); +#endif + rc = syscall(__NR_linkat, -100, sample_1, -100, sample_2, 0); printf("%s%s(AT_FDCWD, \"%s\"%s, AT_FDCWD, \"%s\"%s, 0) = %s\n", my_secontext, "linkat", @@ -108,8 +141,19 @@ main(void) int dfd_old = get_dir_fd("."); char *cwd = get_fd_path(dfd_old); + + update_secontext_field(".", SECONTEXT_TYPE, "default_t"); char *dfd_old_secontext = SECONTEXT_FILE("."); +#ifdef PRINT_SECONTEXT_MISMATCH + if (*dfd_old_secontext && strstr(dfd_old_secontext, "!!") == NULL) + perror_msg_and_fail("Context mismatch not detected: %s", + dfd_old_secontext); + if (*dfd_old_secontext && strstr(dfd_old_secontext, "default_t") == NULL) + perror_msg_and_fail("Context mismatch not detected: %s", + dfd_old_secontext); +#endif + rc = syscall(__NR_linkat, dfd_old, sample_1, -100, sample_2, 0); /* no context printed for sample_2 since file doesn't exist yet */ printf("%s%s(%d%s, \"%s\"%s, AT_FDCWD, \"%s\", 0) = %s\n", diff --git a/tests/options-syntax.test b/tests/options-syntax.test index 765b2f8..848d297 100755 --- a/tests/options-syntax.test +++ b/tests/options-syntax.test @@ -48,8 +48,18 @@ check_e '-t and --absolute-timestamps cannot be provided simultaneously' -t --ti check_e '-t and --absolute-timestamps cannot be provided simultaneously' --absolute-timestamps -ttt -p $$ check_e '-t and --absolute-timestamps cannot be provided simultaneously' -t --timestamps=ns -t -p $$ check_e '-t and --absolute-timestamps cannot be provided simultaneously' --timestamps=ns -t --absolute-timestamps=unix -p $$ -[ -z "$compiled_with_secontext" ] || - check_h "invalid --secontext argument: 'ss'" --secontext=ss +if [ -n "$compiled_with_secontext" ]; then + for opt in '--secontext' '-e secontext'; do + check_e "invalid secontext ''" $opt= + check_e "invalid secontext 'ss'" $opt=ss + check_e "invalid secontext 'ss'" $opt=ss,full,mismatch + check_e "invalid secontext 'ss'" $opt=full,ss,mismatch + check_e "invalid secontext 'ss'" $opt=full,ss + check_e "invalid secontext 'ss'" $opt=full,mismatch,ss + check_e "invalid secontext 'ss'" $opt=!full,ss + check_e "invalid secontext 'ss'" $opt=!full,mismatch,ss + done +fi check_h 'PROG [ARGS] must be specified with -D/--daemonize' -D -p $$ check_h 'PROG [ARGS] must be specified with -D/--daemonize' -DD -p $$ check_h 'PROG [ARGS] must be specified with -D/--daemonize' -DDD -p $$ diff --git a/tests/secontext.c b/tests/secontext.c index 21c6370..848eea9 100644 --- a/tests/secontext.c +++ b/tests/secontext.c @@ -13,8 +13,10 @@ # include # include # include +# include # include # include +# include # include "xmalloc.h" @@ -55,6 +57,79 @@ strip_trailing_newlines(char *context) } static char * +get_type_from_context(const char *full_context) +{ + int saved_errno = errno; + + if (!full_context) + return NULL; + + char *saveptr = NULL; + const char *token; + unsigned int i; + + char *ctx_copy = xstrdup(full_context); + char *context = NULL; + for (token = strtok_r(ctx_copy, ":", &saveptr), i = 0; + token; token = strtok_r(NULL, ":", &saveptr), i++) { + if (i == 2) { + context = xstrdup(token); + break; + } + } + if (!context) + context = xstrdup(full_context); + free(ctx_copy); + + errno = saved_errno; + return context; +} + +static char * +raw_expected_secontext_full_file(const char *filename) +{ + int saved_errno = errno; + char *secontext; + + static struct selabel_handle *hdl; + if (!hdl) { + hdl = selabel_open(SELABEL_CTX_FILE, NULL, 0); + if (!hdl) + perror_msg_and_skip("selabel_open"); + } + + char *resolved = realpath(filename, NULL); + if (!resolved) + perror_msg_and_fail("realpath: %s", filename); + + struct stat statbuf; + if (stat(resolved, &statbuf) < 0) + perror_msg_and_fail("stat: %s", resolved); + + if (selabel_lookup(hdl, &secontext, resolved, statbuf.st_mode) < 0) + perror_msg_and_skip("selabel_lookup: %s", resolved); + free(resolved); + + char *full_secontext = xstrdup(secontext); + freecon(secontext); + errno = saved_errno; + return full_secontext; +} + +static char * +raw_expected_secontext_short_file(const char *filename) +{ + int saved_errno = errno; + + char *ctx = raw_expected_secontext_full_file(filename); + char *type = get_type_from_context(ctx); + free(ctx); + + errno = saved_errno; + return type; +} + +static char * raw_secontext_full_file(const char *filename) { int saved_errno = errno; @@ -75,29 +150,11 @@ raw_secontext_short_file(const char *filename) int saved_errno = errno; char *ctx = raw_secontext_full_file(filename); - if (ctx == NULL) - return ctx; - - char *saveptr = NULL; - const char *token; - unsigned int i; - - char *ctx_copy = xstrdup(ctx); - char *context = NULL; - for (token = strtok_r(ctx_copy, ":", &saveptr), i = 0; - token; token = strtok_r(NULL, ":", &saveptr), i++) { - if (i == 2) { - context = xstrdup(token); - break; - } - } - if (context == NULL) - context = xstrdup(ctx); - free(ctx_copy); + char *type = get_type_from_context(ctx); free(ctx); errno = saved_errno; - return context; + return type; } static char * @@ -121,35 +178,30 @@ raw_secontext_short_pid(pid_t pid) int saved_errno = errno; char *ctx = raw_secontext_full_pid(pid); - if (ctx == NULL) - return ctx; - - char *saveptr = NULL; - const char *token; - int i; - - char *ctx_copy = xstrdup(ctx); - char *context = NULL; - for (token = strtok_r(ctx_copy, ":", &saveptr), i = 0; - token; token = strtok_r(NULL, ":", &saveptr), i++) { - if (i == 2) { - context = xstrdup(token); - break; - } - } - if (context == NULL) - context = xstrdup(ctx); - free(ctx_copy); + char *type = get_type_from_context(ctx); free(ctx); errno = saved_errno; - return context; + return type; } char * -secontext_full_file(const char *filename) +secontext_full_file(const char *filename, bool mismatch) { - return FORMAT_SPACE_BEFORE(raw_secontext_full_file(filename)); + int saved_errno = errno; + char *context = raw_secontext_full_file(filename); + if (context && mismatch) { + char *expected = raw_expected_secontext_full_file(filename); + if (expected && strcmp(context, expected)) { + char *context_mismatch = + xasprintf("%s!!%s", context, expected); + free(context); + context = context_mismatch; + } + free(expected); + } + errno = saved_errno; + return FORMAT_SPACE_BEFORE(context); } char * @@ -159,9 +211,22 @@ secontext_full_pid(pid_t pid) } char * -secontext_short_file(const char *filename) +secontext_short_file(const char *filename, bool mismatch) { - return FORMAT_SPACE_BEFORE(raw_secontext_short_file(filename)); + int saved_errno = errno; + char *context = raw_secontext_short_file(filename); + if (context && mismatch) { + char *expected = raw_expected_secontext_short_file(filename); + if (expected && strcmp(context, expected)) { + char *context_mismatch = + xasprintf("%s!!%s", context, expected); + free(context); + context = context_mismatch; + } + free(expected); + } + errno = saved_errno; + return FORMAT_SPACE_BEFORE(context); } char * @@ -171,31 +236,38 @@ secontext_short_pid(pid_t pid) } void -update_secontext_type(const char *file, const char *newtype) +update_secontext_field(const char *file, enum secontext_field field, + const char *newvalue) { + int saved_errno = errno; + assert(field >= SECONTEXT_USER && field <= SECONTEXT_TYPE); + char *ctx = raw_secontext_full_file(file); if (ctx == NULL) return; char *saveptr = NULL; char *token; - int field; + int nfields; char *split[4]; - for (token = strtok_r(ctx, ":", &saveptr), field = 0; - token; token = strtok_r(NULL, ":", &saveptr), field++) { - assert(field < 4); - split[field] = token; + for (token = strtok_r(ctx, ":", &saveptr), nfields = 0; + token; token = strtok_r(NULL, ":", &saveptr), nfields++) { + assert(nfields < 4); + split[nfields] = token; } - assert(field == 4); + assert(nfields == 4); + + split[field] = (char *)newvalue; char *newcontext = xasprintf("%s:%s:%s:%s", split[0], split[1], - newtype, split[3]); + split[2], split[3]); (void) setfilecon(file, newcontext); free(newcontext); free(ctx); + errno = saved_errno; } #endif /* HAVE_SELINUX_RUNTIME */ diff --git a/tests/secontext.h b/tests/secontext.h index c65f53a..1d0251a 100644 --- a/tests/secontext.h +++ b/tests/secontext.h @@ -9,24 +9,39 @@ #include "xmalloc.h" #include -char *secontext_full_file(const char *) ATTRIBUTE_MALLOC; +char *secontext_full_file(const char *, bool) ATTRIBUTE_MALLOC; char *secontext_full_pid(pid_t) ATTRIBUTE_MALLOC; -char *secontext_short_file(const char *) ATTRIBUTE_MALLOC; +char *secontext_short_file(const char *, bool) ATTRIBUTE_MALLOC; char *secontext_short_pid(pid_t) ATTRIBUTE_MALLOC; +enum secontext_field { + SECONTEXT_USER, + SECONTEXT_ROLE, + SECONTEXT_TYPE +}; + #if defined TEST_SECONTEXT && defined HAVE_SELINUX_RUNTIME -void update_secontext_type(const char *file, const char *newtype); +void update_secontext_field(const char *file, enum secontext_field field, + const char *newvalue); # ifdef PRINT_SECONTEXT_FULL -# define SECONTEXT_FILE(filename) secontext_full_file(filename) +# ifdef PRINT_SECONTEXT_MISMATCH +# define SECONTEXT_FILE(filename) secontext_full_file(filename, true) +# else +# define SECONTEXT_FILE(filename) secontext_full_file(filename, false) +# endif # define SECONTEXT_PID(pid) secontext_full_pid(pid) # else -# define SECONTEXT_FILE(filename) secontext_short_file(filename) +# ifdef PRINT_SECONTEXT_MISMATCH +# define SECONTEXT_FILE(filename) secontext_short_file(filename, true) +# else +# define SECONTEXT_FILE(filename) secontext_short_file(filename, false) +# endif # define SECONTEXT_PID(pid) secontext_short_pid(pid) # endif @@ -34,7 +49,8 @@ void update_secontext_type(const char *file, const char *newtype); #else static inline void -update_secontext_type(const char *file, const char *newtype) +update_secontext_field(const char *file, enum secontext_field field, + const char *newvalue) { } -- 2.1.4