1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include "analyze-syscall-filter.h"
4 #include "analyze.h"
5 #include "fd-util.h"
6 #include "fileio.h"
7 #include "nulstr-util.h"
8 #include "seccomp-util.h"
9 #include "set.h"
10 #include "strv.h"
11 #include "terminal-util.h"
12 
13 #if HAVE_SECCOMP
14 
load_kernel_syscalls(Set ** ret)15 static int load_kernel_syscalls(Set **ret) {
16         _cleanup_set_free_ Set *syscalls = NULL;
17         _cleanup_fclose_ FILE *f = NULL;
18         int r;
19 
20         /* Let's read the available system calls from the list of available tracing events. Slightly dirty,
21          * but good enough for analysis purposes. */
22 
23         f = fopen("/sys/kernel/tracing/available_events", "re");
24         if (!f) {
25                 /* We tried the non-debugfs mount point and that didn't work. If it wasn't mounted, maybe the
26                  * old debugfs mount point works? */
27                 f = fopen("/sys/kernel/debug/tracing/available_events", "re");
28                 if (!f)
29                         return log_full_errno(IN_SET(errno, EPERM, EACCES, ENOENT) ? LOG_DEBUG : LOG_WARNING, errno,
30                                               "Can't read open tracefs' available_events file: %m");
31         }
32 
33         for (;;) {
34                 _cleanup_free_ char *line = NULL;
35                 const char *e;
36 
37                 r = read_line(f, LONG_LINE_MAX, &line);
38                 if (r < 0)
39                         return log_error_errno(r, "Failed to read system call list: %m");
40                 if (r == 0)
41                         break;
42 
43                 e = startswith(line, "syscalls:sys_enter_");
44                 if (!e)
45                         continue;
46 
47                 /* These are named differently inside the kernel than their external name for historical
48                  * reasons. Let's hide them here. */
49                 if (STR_IN_SET(e, "newuname", "newfstat", "newstat", "newlstat", "sysctl"))
50                         continue;
51 
52                 r = set_put_strdup(&syscalls, e);
53                 if (r < 0)
54                         return log_error_errno(r, "Failed to add system call to list: %m");
55         }
56 
57         *ret = TAKE_PTR(syscalls);
58         return 0;
59 }
60 
syscall_set_remove(Set * s,const SyscallFilterSet * set)61 static void syscall_set_remove(Set *s, const SyscallFilterSet *set) {
62         const char *syscall;
63 
64         if (!set)
65                 return;
66 
67         NULSTR_FOREACH(syscall, set->value) {
68                 if (syscall[0] == '@')
69                         continue;
70 
71                 free(set_remove(s, syscall));
72         }
73 }
74 
dump_syscall_filter(const SyscallFilterSet * set)75 static void dump_syscall_filter(const SyscallFilterSet *set) {
76         const char *syscall;
77 
78         printf("%s%s%s\n"
79                "    # %s\n",
80                ansi_highlight(),
81                set->name,
82                ansi_normal(),
83                set->help);
84 
85         NULSTR_FOREACH(syscall, set->value)
86                 printf("    %s%s%s\n", syscall[0] == '@' ? ansi_underline() : "", syscall, ansi_normal());
87 }
88 
verb_syscall_filters(int argc,char * argv[],void * userdata)89 int verb_syscall_filters(int argc, char *argv[], void *userdata) {
90         bool first = true;
91 
92         pager_open(arg_pager_flags);
93 
94         if (strv_isempty(strv_skip(argv, 1))) {
95                 _cleanup_set_free_ Set *kernel = NULL, *known = NULL;
96                 const char *sys;
97                 int k = 0;  /* explicit initialization to appease gcc */
98 
99                 NULSTR_FOREACH(sys, syscall_filter_sets[SYSCALL_FILTER_SET_KNOWN].value)
100                         if (set_put_strdup(&known, sys) < 0)
101                                 return log_oom();
102 
103                 if (!arg_quiet)
104                         k = load_kernel_syscalls(&kernel);
105 
106                 for (int i = 0; i < _SYSCALL_FILTER_SET_MAX; i++) {
107                         const SyscallFilterSet *set = syscall_filter_sets + i;
108                         if (!first)
109                                 puts("");
110 
111                         dump_syscall_filter(set);
112                         syscall_set_remove(kernel, set);
113                         if (i != SYSCALL_FILTER_SET_KNOWN)
114                                 syscall_set_remove(known, set);
115                         first = false;
116                 }
117 
118                 if (arg_quiet)  /* Let's not show the extra stuff in quiet mode */
119                         return 0;
120 
121                 if (!set_isempty(known)) {
122                         _cleanup_free_ char **l = NULL;
123 
124                         printf("\n"
125                                "# %sUngrouped System Calls%s (known but not included in any of the groups except @known):\n",
126                                ansi_highlight(), ansi_normal());
127 
128                         l = set_get_strv(known);
129                         if (!l)
130                                 return log_oom();
131 
132                         strv_sort(l);
133 
134                         STRV_FOREACH(syscall, l)
135                                 printf("#   %s\n", *syscall);
136                 }
137 
138                 if (k < 0) {
139                         fputc('\n', stdout);
140                         fflush(stdout);
141                         if (!arg_quiet)
142                                 log_notice_errno(k, "# Not showing unlisted system calls, couldn't retrieve kernel system call list: %m");
143                 } else if (!set_isempty(kernel)) {
144                         _cleanup_free_ char **l = NULL;
145 
146                         printf("\n"
147                                "# %sUnlisted System Calls%s (supported by the local kernel, but not included in any of the groups listed above):\n",
148                                ansi_highlight(), ansi_normal());
149 
150                         l = set_get_strv(kernel);
151                         if (!l)
152                                 return log_oom();
153 
154                         strv_sort(l);
155 
156                         STRV_FOREACH(syscall, l)
157                                 printf("#   %s\n", *syscall);
158                 }
159         } else
160                 STRV_FOREACH(name, strv_skip(argv, 1)) {
161                         const SyscallFilterSet *set;
162 
163                         if (!first)
164                                 puts("");
165 
166                         set = syscall_filter_set_find(*name);
167                         if (!set) {
168                                 /* make sure the error appears below normal output */
169                                 fflush(stdout);
170 
171                                 return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
172                                                        "Filter set \"%s\" not found.", *name);
173                         }
174 
175                         dump_syscall_filter(set);
176                         first = false;
177                 }
178 
179         return 0;
180 }
181 
182 #else
verb_syscall_filters(int argc,char * argv[],void * userdata)183 int verb_syscall_filters(int argc, char *argv[], void *userdata) {
184         return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Not compiled with syscall filters, sorry.");
185 }
186 #endif
187