| /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
| |
| #include "analyze-syscall-filter.h" |
| #include "analyze.h" |
| #include "fd-util.h" |
| #include "fileio.h" |
| #include "nulstr-util.h" |
| #include "seccomp-util.h" |
| #include "set.h" |
| #include "strv.h" |
| #include "terminal-util.h" |
| |
| #if HAVE_SECCOMP |
| |
| static int load_kernel_syscalls(Set **ret) { |
| _cleanup_set_free_ Set *syscalls = NULL; |
| _cleanup_fclose_ FILE *f = NULL; |
| int r; |
| |
| /* Let's read the available system calls from the list of available tracing events. Slightly dirty, |
| * but good enough for analysis purposes. */ |
| |
| f = fopen("/sys/kernel/tracing/available_events", "re"); |
| if (!f) { |
| /* We tried the non-debugfs mount point and that didn't work. If it wasn't mounted, maybe the |
| * old debugfs mount point works? */ |
| f = fopen("/sys/kernel/debug/tracing/available_events", "re"); |
| if (!f) |
| return log_full_errno(IN_SET(errno, EPERM, EACCES, ENOENT) ? LOG_DEBUG : LOG_WARNING, errno, |
| "Can't read open tracefs' available_events file: %m"); |
| } |
| |
| for (;;) { |
| _cleanup_free_ char *line = NULL; |
| const char *e; |
| |
| r = read_line(f, LONG_LINE_MAX, &line); |
| if (r < 0) |
| return log_error_errno(r, "Failed to read system call list: %m"); |
| if (r == 0) |
| break; |
| |
| e = startswith(line, "syscalls:sys_enter_"); |
| if (!e) |
| continue; |
| |
| /* These are named differently inside the kernel than their external name for historical |
| * reasons. Let's hide them here. */ |
| if (STR_IN_SET(e, "newuname", "newfstat", "newstat", "newlstat", "sysctl")) |
| continue; |
| |
| r = set_put_strdup(&syscalls, e); |
| if (r < 0) |
| return log_error_errno(r, "Failed to add system call to list: %m"); |
| } |
| |
| *ret = TAKE_PTR(syscalls); |
| return 0; |
| } |
| |
| static int syscall_set_add(Set **s, const SyscallFilterSet *set) { |
| int r; |
| |
| assert(s); |
| |
| if (!set) |
| return 0; |
| |
| NULSTR_FOREACH(sc, set->value) { |
| if (sc[0] == '@') |
| continue; |
| |
| r = set_put_strdup(s, sc); |
| if (r < 0) |
| return r; |
| } |
| |
| return 0; |
| } |
| |
| static void syscall_set_remove(Set *s, const SyscallFilterSet *set) { |
| if (!set) |
| return; |
| |
| NULSTR_FOREACH(sc, set->value) { |
| if (sc[0] == '@') |
| continue; |
| |
| free(set_remove(s, sc)); |
| } |
| } |
| |
| static void dump_syscall_filter(const SyscallFilterSet *set) { |
| printf("%s%s%s\n" |
| " # %s\n", |
| ansi_highlight(), |
| set->name, |
| ansi_normal(), |
| set->help); |
| |
| NULSTR_FOREACH(syscall, set->value) |
| printf(" %s%s%s\n", syscall[0] == '@' ? ansi_underline() : "", syscall, ansi_normal()); |
| } |
| |
| int verb_syscall_filters(int argc, char *argv[], void *userdata) { |
| bool first = true; |
| int r; |
| |
| pager_open(arg_pager_flags); |
| |
| if (strv_isempty(strv_skip(argv, 1))) { |
| _cleanup_set_free_ Set *kernel = NULL, *known = NULL; |
| int k = 0; /* explicit initialization to appease gcc */ |
| |
| r = syscall_set_add(&known, syscall_filter_sets + SYSCALL_FILTER_SET_KNOWN); |
| if (r < 0) |
| return log_error_errno(r, "Failed to prepare set of known system calls: %m"); |
| |
| if (!arg_quiet) |
| k = load_kernel_syscalls(&kernel); |
| |
| for (int i = 0; i < _SYSCALL_FILTER_SET_MAX; i++) { |
| const SyscallFilterSet *set = syscall_filter_sets + i; |
| if (!first) |
| puts(""); |
| |
| dump_syscall_filter(set); |
| syscall_set_remove(kernel, set); |
| if (i != SYSCALL_FILTER_SET_KNOWN) |
| syscall_set_remove(known, set); |
| first = false; |
| } |
| |
| if (arg_quiet) /* Let's not show the extra stuff in quiet mode */ |
| return 0; |
| |
| if (!set_isempty(known)) { |
| _cleanup_free_ char **l = NULL; |
| |
| printf("\n" |
| "# %sUngrouped System Calls%s (known but not included in any of the groups except @known):\n", |
| ansi_highlight(), ansi_normal()); |
| |
| l = set_get_strv(known); |
| if (!l) |
| return log_oom(); |
| |
| strv_sort(l); |
| |
| STRV_FOREACH(syscall, l) |
| printf("# %s\n", *syscall); |
| } |
| |
| if (k < 0) { |
| fputc('\n', stdout); |
| fflush(stdout); |
| if (!arg_quiet) |
| log_notice_errno(k, "# Not showing unlisted system calls, couldn't retrieve kernel system call list: %m"); |
| } else if (!set_isempty(kernel)) { |
| _cleanup_free_ char **l = NULL; |
| |
| printf("\n" |
| "# %sUnlisted System Calls%s (supported by the local kernel, but not included in any of the groups listed above):\n", |
| ansi_highlight(), ansi_normal()); |
| |
| l = set_get_strv(kernel); |
| if (!l) |
| return log_oom(); |
| |
| strv_sort(l); |
| |
| STRV_FOREACH(syscall, l) |
| printf("# %s\n", *syscall); |
| } |
| } else |
| STRV_FOREACH(name, strv_skip(argv, 1)) { |
| const SyscallFilterSet *set; |
| |
| if (!first) |
| puts(""); |
| |
| set = syscall_filter_set_find(*name); |
| if (!set) { |
| /* make sure the error appears below normal output */ |
| fflush(stdout); |
| |
| return log_error_errno(SYNTHETIC_ERRNO(ENOENT), |
| "Filter set \"%s\" not found.", *name); |
| } |
| |
| dump_syscall_filter(set); |
| first = false; |
| } |
| |
| return EXIT_SUCCESS; |
| } |
| |
| #else |
| int verb_syscall_filters(int argc, char *argv[], void *userdata) { |
| return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Not compiled with syscall filters, sorry."); |
| } |
| #endif |