/src/suricata7/src/util-landlock.c
Line | Count | Source |
1 | | /* Copyright (C) 2022 Open Information Security Foundation |
2 | | * |
3 | | * You can copy, redistribute or modify this Program under the terms of |
4 | | * the GNU General Public License version 2 as published by the Free |
5 | | * Software Foundation. |
6 | | * |
7 | | * This program is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * version 2 along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
15 | | * 02110-1301, USA. |
16 | | */ |
17 | | |
18 | | /** |
19 | | * \file |
20 | | * |
21 | | * \author Eric Leblond <el@stamus-networks.com> |
22 | | */ |
23 | | |
24 | | #include "suricata.h" |
25 | | #include "feature.h" |
26 | | #include "util-conf.h" |
27 | | #include "util-file.h" |
28 | | #include "util-landlock.h" |
29 | | #include "util-mem.h" |
30 | | #include "util-path.h" |
31 | | |
32 | | #ifndef HAVE_LINUX_LANDLOCK_H |
33 | | |
34 | | void LandlockSandboxing(SCInstance *suri) |
35 | 0 | { |
36 | 0 | return; |
37 | 0 | } |
38 | | |
39 | | #else /* HAVE_LINUX_LANDLOCK_H */ |
40 | | |
41 | | #include <linux/landlock.h> |
42 | | |
43 | | #ifndef landlock_create_ruleset |
44 | | static inline int landlock_create_ruleset( |
45 | | const struct landlock_ruleset_attr *const attr, const size_t size, const __u32 flags) |
46 | | { |
47 | | return syscall(__NR_landlock_create_ruleset, attr, size, flags); |
48 | | } |
49 | | #endif |
50 | | |
51 | | #ifndef landlock_add_rule |
52 | | static inline int landlock_add_rule(const int ruleset_fd, const enum landlock_rule_type rule_type, |
53 | | const void *const rule_attr, const __u32 flags) |
54 | | { |
55 | | return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr, flags); |
56 | | } |
57 | | #endif |
58 | | |
59 | | #ifndef landlock_restrict_self |
60 | | static inline int landlock_restrict_self(const int ruleset_fd, const __u32 flags) |
61 | | { |
62 | | return syscall(__NR_landlock_restrict_self, ruleset_fd, flags); |
63 | | } |
64 | | #endif |
65 | | |
66 | | #ifndef LANDLOCK_ACCESS_FS_REFER |
67 | | #define LANDLOCK_ACCESS_FS_REFER (1ULL << 13) |
68 | | #endif |
69 | | |
70 | | #define _LANDLOCK_ACCESS_FS_WRITE \ |
71 | | (LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_REMOVE_DIR | \ |
72 | | LANDLOCK_ACCESS_FS_REMOVE_FILE | LANDLOCK_ACCESS_FS_MAKE_CHAR | \ |
73 | | LANDLOCK_ACCESS_FS_MAKE_DIR | LANDLOCK_ACCESS_FS_MAKE_REG | \ |
74 | | LANDLOCK_ACCESS_FS_MAKE_SOCK | LANDLOCK_ACCESS_FS_MAKE_FIFO | \ |
75 | | LANDLOCK_ACCESS_FS_MAKE_BLOCK | LANDLOCK_ACCESS_FS_MAKE_SYM | \ |
76 | | LANDLOCK_ACCESS_FS_REFER) |
77 | | |
78 | | #define _LANDLOCK_ACCESS_FS_READ (LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR) |
79 | | |
80 | | #define _LANDLOCK_SURI_ACCESS_FS_WRITE \ |
81 | | (LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_MAKE_DIR | LANDLOCK_ACCESS_FS_MAKE_REG | \ |
82 | | LANDLOCK_ACCESS_FS_REMOVE_FILE | LANDLOCK_ACCESS_FS_MAKE_SOCK) |
83 | | |
84 | | struct landlock_ruleset { |
85 | | int fd; |
86 | | struct landlock_ruleset_attr attr; |
87 | | }; |
88 | | |
89 | | static inline struct landlock_ruleset *LandlockCreateRuleset(void) |
90 | | { |
91 | | struct landlock_ruleset *ruleset = SCCalloc(1, sizeof(struct landlock_ruleset)); |
92 | | if (ruleset == NULL) { |
93 | | SCLogError("Can't alloc landlock ruleset"); |
94 | | return NULL; |
95 | | } |
96 | | |
97 | | ruleset->attr.handled_access_fs = |
98 | | _LANDLOCK_ACCESS_FS_READ | _LANDLOCK_ACCESS_FS_WRITE | LANDLOCK_ACCESS_FS_EXECUTE; |
99 | | |
100 | | int abi = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION); |
101 | | if (abi < 0) { |
102 | | SCFree(ruleset); |
103 | | return NULL; |
104 | | } |
105 | | if (abi < 2) { |
106 | | if (RequiresFeature(FEATURE_OUTPUT_FILESTORE)) { |
107 | | SCLogError("Landlock disabled: need Linux 5.19+ for file store support"); |
108 | | SCFree(ruleset); |
109 | | return NULL; |
110 | | } else { |
111 | | ruleset->attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_REFER; |
112 | | } |
113 | | } |
114 | | |
115 | | ruleset->fd = landlock_create_ruleset(&ruleset->attr, sizeof(ruleset->attr), 0); |
116 | | if (ruleset->fd < 0) { |
117 | | SCFree(ruleset); |
118 | | SCLogError("Can't create landlock ruleset"); |
119 | | return NULL; |
120 | | } |
121 | | return ruleset; |
122 | | } |
123 | | |
124 | | static inline void LandlockEnforceRuleset(struct landlock_ruleset *ruleset) |
125 | | { |
126 | | if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) { |
127 | | SCLogError("Can't self restrict (prctl phase): %s", strerror(errno)); |
128 | | return; |
129 | | } |
130 | | if (landlock_restrict_self(ruleset->fd, 0)) { |
131 | | SCLogError("Can't self restrict (landlock phase): %s", strerror(errno)); |
132 | | } |
133 | | } |
134 | | |
135 | | static int LandlockSandboxingAddRule( |
136 | | struct landlock_ruleset *ruleset, const char *directory, uint64_t permission) |
137 | | { |
138 | | struct landlock_path_beneath_attr path_beneath = { |
139 | | .allowed_access = permission & ruleset->attr.handled_access_fs, |
140 | | }; |
141 | | |
142 | | int dir_fd = open(directory, O_PATH | O_CLOEXEC | O_DIRECTORY); |
143 | | if (dir_fd == -1) { |
144 | | SCLogError("Can't open %s", directory); |
145 | | return -1; |
146 | | } |
147 | | path_beneath.parent_fd = dir_fd; |
148 | | |
149 | | if (landlock_add_rule(ruleset->fd, LANDLOCK_RULE_PATH_BENEATH, &path_beneath, 0)) { |
150 | | SCLogError("Can't add write rule: %s", strerror(errno)); |
151 | | close(dir_fd); |
152 | | return -1; |
153 | | } |
154 | | |
155 | | close(dir_fd); |
156 | | return 0; |
157 | | } |
158 | | |
159 | | static inline void LandlockSandboxingWritePath( |
160 | | struct landlock_ruleset *ruleset, const char *directory) |
161 | | { |
162 | | if (LandlockSandboxingAddRule(ruleset, directory, _LANDLOCK_SURI_ACCESS_FS_WRITE) == 0) { |
163 | | SCLogConfig("Added write permission to '%s'", directory); |
164 | | } |
165 | | } |
166 | | |
167 | | static inline void LandlockSandboxingReadPath( |
168 | | struct landlock_ruleset *ruleset, const char *directory) |
169 | | { |
170 | | if (LandlockSandboxingAddRule(ruleset, directory, _LANDLOCK_ACCESS_FS_READ) == 0) { |
171 | | SCLogConfig("Added read permission to '%s'", directory); |
172 | | } |
173 | | } |
174 | | |
175 | | void LandlockSandboxing(SCInstance *suri) |
176 | | { |
177 | | /* Read configuration variable and exit if no enforcement */ |
178 | | int conf_status; |
179 | | if (ConfGetBool("security.landlock.enabled", &conf_status) == 0) { |
180 | | conf_status = 0; |
181 | | } |
182 | | if (!conf_status) { |
183 | | SCLogConfig("Landlock is not enabled in configuration"); |
184 | | return; |
185 | | } |
186 | | struct landlock_ruleset *ruleset = LandlockCreateRuleset(); |
187 | | if (ruleset == NULL) { |
188 | | SCLogError("Kernel does not support Landlock"); |
189 | | return; |
190 | | } |
191 | | |
192 | | LandlockSandboxingWritePath(ruleset, ConfigGetLogDirectory()); |
193 | | struct stat sb; |
194 | | if (stat(ConfigGetDataDirectory(), &sb) == 0) { |
195 | | LandlockSandboxingAddRule(ruleset, ConfigGetDataDirectory(), |
196 | | _LANDLOCK_SURI_ACCESS_FS_WRITE | _LANDLOCK_ACCESS_FS_READ); |
197 | | } |
198 | | if (suri->run_mode == RUNMODE_PCAP_FILE) { |
199 | | const char *pcap_file; |
200 | | if (ConfGet("pcap-file.file", &pcap_file) == 1) { |
201 | | char *file_name = SCStrdup(pcap_file); |
202 | | if (file_name != NULL) { |
203 | | struct stat statbuf; |
204 | | if (stat(file_name, &statbuf) != -1) { |
205 | | if (S_ISDIR(statbuf.st_mode)) { |
206 | | LandlockSandboxingReadPath(ruleset, file_name); |
207 | | } else { |
208 | | LandlockSandboxingReadPath(ruleset, dirname(file_name)); |
209 | | } |
210 | | } else { |
211 | | SCLogError("Can't open pcap file"); |
212 | | } |
213 | | SCFree(file_name); |
214 | | } |
215 | | } |
216 | | } |
217 | | if (suri->sig_file) { |
218 | | char *file_name = SCStrdup(suri->sig_file); |
219 | | if (file_name != NULL) { |
220 | | LandlockSandboxingReadPath(ruleset, dirname(file_name)); |
221 | | SCFree(file_name); |
222 | | } |
223 | | } |
224 | | if (suri->pid_filename) { |
225 | | char *file_name = SCStrdup(suri->pid_filename); |
226 | | if (file_name != NULL) { |
227 | | LandlockSandboxingWritePath(ruleset, dirname(file_name)); |
228 | | SCFree(file_name); |
229 | | } |
230 | | } |
231 | | if (ConfUnixSocketIsEnable()) { |
232 | | const char *socketname; |
233 | | if (ConfGet("unix-command.filename", &socketname) == 1) { |
234 | | if (PathIsAbsolute(socketname)) { |
235 | | char *file_name = SCStrdup(socketname); |
236 | | if (file_name != NULL) { |
237 | | LandlockSandboxingWritePath(ruleset, dirname(file_name)); |
238 | | SCFree(file_name); |
239 | | } |
240 | | } else { |
241 | | LandlockSandboxingWritePath(ruleset, LOCAL_STATE_DIR "/run/suricata/"); |
242 | | } |
243 | | } else { |
244 | | LandlockSandboxingWritePath(ruleset, LOCAL_STATE_DIR "/run/suricata/"); |
245 | | } |
246 | | } |
247 | | if (suri->sig_file_exclusive == FALSE) { |
248 | | const char *rule_path; |
249 | | if (ConfGet("default-rule-path", &rule_path) == 1 && rule_path) { |
250 | | LandlockSandboxingReadPath(ruleset, rule_path); |
251 | | } |
252 | | } |
253 | | |
254 | | ConfNode *read_dirs = ConfGetNode("security.landlock.directories.read"); |
255 | | if (read_dirs) { |
256 | | if (!ConfNodeIsSequence(read_dirs)) { |
257 | | SCLogWarning("Invalid security.landlock.directories.read configuration section: " |
258 | | "expected a list of directory names."); |
259 | | } else { |
260 | | ConfNode *directory; |
261 | | TAILQ_FOREACH (directory, &read_dirs->head, next) { |
262 | | LandlockSandboxingReadPath(ruleset, directory->val); |
263 | | } |
264 | | } |
265 | | } |
266 | | ConfNode *write_dirs = ConfGetNode("security.landlock.directories.write"); |
267 | | if (write_dirs) { |
268 | | if (!ConfNodeIsSequence(write_dirs)) { |
269 | | SCLogWarning("Invalid security.landlock.directories.write configuration section: " |
270 | | "expected a list of directory names."); |
271 | | } else { |
272 | | ConfNode *directory; |
273 | | TAILQ_FOREACH (directory, &write_dirs->head, next) { |
274 | | LandlockSandboxingWritePath(ruleset, directory->val); |
275 | | } |
276 | | } |
277 | | } |
278 | | LandlockEnforceRuleset(ruleset); |
279 | | SCFree(ruleset); |
280 | | } |
281 | | |
282 | | #endif /* HAVE_LINUX_LANDLOCK_H */ |