]>
Commit | Line | Data |
---|---|---|
0c931ee9 MT |
1 | /*############################################################################# |
2 | # # | |
3 | # Pakfire - The IPFire package management system # | |
4 | # Copyright (C) 2021 Pakfire development team # | |
5 | # # | |
6 | # This program is free software: you can redistribute it and/or modify # | |
7 | # it under the terms of the GNU General Public License as published by # | |
8 | # the Free Software Foundation, either version 3 of the License, or # | |
9 | # (at your option) any later version. # | |
10 | # # | |
11 | # This program is distributed in the hope that it will be useful, # | |
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # | |
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # | |
14 | # GNU General Public License for more details. # | |
15 | # # | |
16 | # You should have received a copy of the GNU General Public License # | |
17 | # along with this program. If not, see <http://www.gnu.org/licenses/>. # | |
18 | # # | |
19 | #############################################################################*/ | |
20 | ||
21 | #include <sys/stat.h> | |
22 | ||
517708c8 MT |
23 | // Enable legacy logging |
24 | #define PAKFIRE_LEGACY_LOGGING | |
25 | ||
0c931ee9 MT |
26 | #include <pakfire/fhs.h> |
27 | #include <pakfire/file.h> | |
28 | #include <pakfire/logging.h> | |
29 | #include <pakfire/pakfire.h> | |
30 | #include <pakfire/util.h> | |
31 | ||
32 | /* | |
33 | This struct defines any FHS checks. | |
34 | ||
35 | They are being processed in order from top to bottom which is why we are starting | |
36 | with some more prominent matches and have the less important stuff at the bottom. | |
37 | */ | |
38 | static const struct pakfire_fhs_check { | |
39 | const char* path; | |
84a9a7b7 | 40 | const mode_t type; |
84a9a7b7 | 41 | const mode_t perms; |
26f2d651 MT |
42 | const char* uname; |
43 | const char* gname; | |
08ec3fc4 MT |
44 | enum pakfire_fhs_check_flags { |
45 | PAKFIRE_FHS_MUSTNOTEXIST = (1 << 0), | |
760e1615 | 46 | PAKFIRE_FHS_NOEXEC = (1 << 1), |
08ec3fc4 | 47 | } flags; |
0c931ee9 MT |
48 | } pakfire_fhs_check[] = { |
49 | // /usr | |
21bd82b0 MT |
50 | { "/usr", S_IFDIR, 0755, "root", "root", 0 }, |
51 | { "/usr/bin", S_IFDIR, 0755, "root", "root", 0 }, | |
52 | { "/usr/include", S_IFDIR, 0755, "root", "root", 0 }, | |
53 | { "/usr/lib", S_IFDIR, 0755, "root", "root", 0 }, | |
54 | { "/usr/lib64", S_IFDIR, 0755, "root", "root", 0 }, | |
55 | { "/usr/sbin", S_IFDIR, 0755, "root", "root", 0 }, | |
56 | { "/usr/share", S_IFDIR, 0755, "root", "root", 0 }, | |
57 | { "/usr/src", S_IFDIR, 0755, "root", "root", 0 }, | |
9cf6f5e5 | 58 | { "/usr/src/kernels", S_IFDIR, 0755, "root", "root", 0 }, |
0c931ee9 | 59 | |
fed053b7 | 60 | // Allow no further files in /usr |
21bd82b0 | 61 | { "/usr/*", 0, 0, NULL, NULL, PAKFIRE_FHS_MUSTNOTEXIST }, |
fed053b7 MT |
62 | |
63 | // Allow no files in /usr/src except some kernel source | |
64 | { "/usr/src/kernels/**", 0, 0, "root", "root", 0 }, | |
21bd82b0 | 65 | { "/usr/src/**", 0, 0, NULL, NULL, PAKFIRE_FHS_MUSTNOTEXIST }, |
b3c8a073 | 66 | |
f6dbfb8f | 67 | // There cannot be any subdirectories in /usr/bin & /usr/sbin |
513dc5a9 MT |
68 | { "/usr/bin/*", S_IFDIR, 0, NULL, NULL, PAKFIRE_FHS_MUSTNOTEXIST }, |
69 | { "/usr/sbin/*", S_IFDIR, 0, NULL, NULL, PAKFIRE_FHS_MUSTNOTEXIST }, | |
f6dbfb8f | 70 | |
033373b3 | 71 | // Permitted setuid binaries |
35c9d82a MT |
72 | { "/usr/bin/gpasswd", S_IFREG, S_ISUID|0755, "root", "root", 0 }, |
73 | { "/usr/bin/ksu", S_IFREG, S_ISUID|0755, "root", "root", 0 }, | |
844d9546 | 74 | { "/usr/bin/passwd", S_IFREG, S_ISUID|0755, "root", "root", 0 }, |
35c9d82a | 75 | { "/usr/bin/pkexec", S_IFREG, S_ISUID|0755, "root", "root", 0 }, |
844d9546 | 76 | { "/usr/bin/sudo", S_IFREG, S_ISUID|0755, "root", "root", 0 }, |
033373b3 | 77 | |
2250db76 | 78 | // Any files in /usr/{,s}bin must be owned by root and have 0755 |
21bd82b0 MT |
79 | { "/usr/bin/*", S_IFREG, 0755, "root", "root", 0 }, |
80 | { "/usr/sbin/*", S_IFREG, 0755, "root", "root", 0 }, | |
2250db76 | 81 | |
ad6be9f7 MT |
82 | // Shared Libraries must be executable |
83 | { "/usr/lib64/*.so.*", S_IFREG, 0755, "root", "root", 0 }, | |
84 | { "/usr/lib64/**/*.so", S_IFREG, 0755, "root", "root", 0 }, | |
85 | ||
68501d96 MT |
86 | // Tolerate runtime linkers in /usr/lib |
87 | { "/usr/lib/ld-*.so.*", S_IFREG, 0755, "root", "root", 0 }, | |
88 | ||
ad6be9f7 MT |
89 | // Shared Libraries must not exist in /usr/lib |
90 | { "/usr/lib/*.so*", S_IFREG, 0, NULL, NULL, PAKFIRE_FHS_MUSTNOTEXIST }, | |
91 | ||
b5416a4f MT |
92 | // /usr/include: Ensure that: |
93 | // * All files are non-executable and belong to root | |
94 | // * All directories have 0755 and belong to root | |
21bd82b0 MT |
95 | { "/usr/include/**", S_IFREG, 0644, "root", "root", 0 }, |
96 | { "/usr/include/**", S_IFDIR, 0755, "root", "root", 0 }, | |
97 | ||
98 | // Firmware must not be executable | |
99 | { "/usr/lib/firmware/**", S_IFREG, 0644, "root", "root", 0 }, | |
100 | { "/usr/lib/firmware/**", S_IFDIR, 0755, "root", "root", 0 }, | |
b5416a4f | 101 | |
0c931ee9 | 102 | // /var |
2b1d53fd AB |
103 | // The first 0 in the permissions defines that the number is octal whether |
104 | // three or four digits are used | |
105 | { "/var", S_IFDIR, 0755, "root", "root", 0 }, | |
106 | { "/var/cache", S_IFDIR, 0755, "root", "root", 0 }, | |
107 | { "/var/db", S_IFDIR, 0755, "root", "root", 0 }, | |
108 | { "/var/empty", S_IFDIR, 0755, "root", "root", 0 }, | |
109 | { "/var/lib", S_IFDIR, 0755, "root", "root", 0 }, | |
110 | { "/var/log", S_IFDIR, 0755, "root", "root", 0 }, | |
111 | { "/var/mail", S_IFDIR, 02775, "root", "mail", 0 }, | |
112 | { "/var/opt", S_IFDIR, 0755, "root", "root", 0 }, | |
113 | { "/var/run", S_IFLNK, 0755, "root", "root", 0 }, | |
114 | { "/var/spool", S_IFDIR, 0755, "root", "root", 0 }, | |
115 | { "/var/tmp", S_IFDIR, 0755, "root", "root", 0 }, | |
386b30c2 MT |
116 | |
117 | // Do not allow any subdirectories in /var | |
21bd82b0 MT |
118 | { "/var/*", 0, 0, NULL, NULL, PAKFIRE_FHS_MUSTNOTEXIST }, |
119 | { "/var/empty/**", 0, 0, NULL, NULL, PAKFIRE_FHS_MUSTNOTEXIST }, | |
120 | { "/var/tmp/**", 0, 0, NULL, NULL, PAKFIRE_FHS_MUSTNOTEXIST }, | |
0c931ee9 | 121 | |
edd297db MT |
122 | // No files in /var may be executable |
123 | { "/var/**", S_IFREG, 0, NULL, NULL, PAKFIRE_FHS_NOEXEC }, | |
124 | ||
0c931ee9 | 125 | // /boot |
21bd82b0 MT |
126 | { "/boot", S_IFDIR, 0755, "root", "root", 0 }, |
127 | { "/boot/efi", S_IFDIR, 0755, "root", "root", 0 }, | |
0c931ee9 | 128 | |
4116016e MT |
129 | // All files in /boot must be owned by root |
130 | { "/boot/**", S_IFREG, 0, "root", "root", 0, }, | |
131 | { "/boot/**", S_IFDIR, 0, "root", "root", 0, }, | |
132 | ||
0c931ee9 | 133 | // /dev (nothing may exist in it) |
21bd82b0 MT |
134 | { "/dev", S_IFDIR, 0755, "root", "root", 0 }, |
135 | { "/dev/**", 0, 0, NULL, NULL, PAKFIRE_FHS_MUSTNOTEXIST }, | |
0c931ee9 MT |
136 | |
137 | // /etc | |
21bd82b0 | 138 | { "/etc", S_IFDIR, 0755, "root", "root", 0 }, |
0c931ee9 MT |
139 | |
140 | // /home | |
21bd82b0 MT |
141 | { "/home", S_IFDIR, 0755, "root", "root", 0 }, |
142 | { "/home/**", 0, 0, NULL, NULL, PAKFIRE_FHS_MUSTNOTEXIST }, | |
0c931ee9 MT |
143 | |
144 | // /opt | |
21bd82b0 | 145 | { "/opt", S_IFDIR, 0755, "root", "root", 0 }, |
0c931ee9 MT |
146 | // These directories belong to the "local administrator" |
147 | // https://refspecs.linuxfoundation.org/FHS_3.0/fhs/ch03s13.html | |
21bd82b0 MT |
148 | { "/opt/bin", 0, 0, NULL, NULL, PAKFIRE_FHS_MUSTNOTEXIST }, |
149 | { "/opt/doc", 0, 0, NULL, NULL, PAKFIRE_FHS_MUSTNOTEXIST }, | |
150 | { "/opt/include", 0, 0, NULL, NULL, PAKFIRE_FHS_MUSTNOTEXIST }, | |
151 | { "/opt/info", 0, 0, NULL, NULL, PAKFIRE_FHS_MUSTNOTEXIST }, | |
152 | { "/opt/lib", 0, 0, NULL, NULL, PAKFIRE_FHS_MUSTNOTEXIST }, | |
153 | { "/opt/man", 0, 0, NULL, NULL, PAKFIRE_FHS_MUSTNOTEXIST }, | |
0c931ee9 MT |
154 | |
155 | // /proc | |
21bd82b0 MT |
156 | { "/proc", S_IFDIR, 0755, "root", "root", 0 }, |
157 | { "/proc/**", 0, 0, NULL, NULL, PAKFIRE_FHS_MUSTNOTEXIST }, | |
0c931ee9 | 158 | |
727f3b22 MT |
159 | // root |
160 | { "/root", S_IFDIR, 0700, "root", "root", 0 }, | |
c905305d | 161 | { "/root/.*", S_IFREG, 0644, "root", "root", 0 }, |
727f3b22 MT |
162 | { "/root/**", 0, 0, NULL, NULL, PAKFIRE_FHS_MUSTNOTEXIST }, |
163 | ||
0c931ee9 | 164 | // /run |
21bd82b0 MT |
165 | { "/run", S_IFDIR, 0755, "root", "root", 0 }, |
166 | { "/run/**", 0, 0, NULL, NULL, PAKFIRE_FHS_MUSTNOTEXIST }, | |
0c931ee9 MT |
167 | |
168 | // /sys | |
21bd82b0 MT |
169 | { "/sys", S_IFDIR, 0755, "root", "root", 0 }, |
170 | { "/sys/**", 0, 0, NULL, NULL, PAKFIRE_FHS_MUSTNOTEXIST }, | |
0c931ee9 MT |
171 | |
172 | // /tmp | |
2b1d53fd | 173 | { "/tmp", S_IFDIR, 01755, "root", "root", 0 }, |
21bd82b0 | 174 | { "/tmp/**", 0, 0, NULL, NULL, PAKFIRE_FHS_MUSTNOTEXIST }, |
0c931ee9 MT |
175 | |
176 | // FHS Directories | |
21bd82b0 MT |
177 | { "/media", S_IFDIR, 0755, "root", "root", 0 }, |
178 | { "/mnt", S_IFDIR, 0755, "root", "root", 0 }, | |
179 | { "/srv", S_IFDIR, 0755, "root", "root", 0 }, | |
0c931ee9 MT |
180 | |
181 | // /bin, /sbin, /lib, and /lib64 have to be symlinks | |
21bd82b0 MT |
182 | { "/bin", S_IFLNK, 0777, NULL, NULL, 0 }, |
183 | { "/lib", S_IFLNK, 0777, NULL, NULL, 0 }, | |
184 | { "/lib64", S_IFLNK, 0777, NULL, NULL, 0 }, | |
185 | { "/sbin", S_IFLNK, 0777, NULL, NULL, 0 }, | |
0c931ee9 MT |
186 | |
187 | // There cannot be anything else in / | |
21bd82b0 | 188 | { "/*", 0, 0, NULL, NULL, PAKFIRE_FHS_MUSTNOTEXIST }, |
0c931ee9 MT |
189 | |
190 | // Catch all so that we won't throw an error | |
21bd82b0 | 191 | { "/**", 0, 0, NULL, NULL, 0 }, |
0c931ee9 MT |
192 | |
193 | // Sentinel | |
194 | { NULL }, | |
195 | }; | |
196 | ||
197 | static const struct pakfire_fhs_check* pakfire_fhs_find_check( | |
84a9a7b7 | 198 | struct pakfire* pakfire, struct pakfire_file* file) { |
0c931ee9 MT |
199 | const struct pakfire_fhs_check* check = NULL; |
200 | int r; | |
201 | ||
84a9a7b7 MT |
202 | // Fetch the file type |
203 | const mode_t type = pakfire_file_get_type(file); | |
204 | ||
205 | // Fetch the path | |
206 | const char* path = pakfire_file_get_path(file); | |
207 | ||
0c931ee9 MT |
208 | // Walk through all possible checks |
209 | for (check = pakfire_fhs_check; check->path; check++) { | |
84a9a7b7 MT |
210 | // Skip this check if the filetype doesn't match |
211 | if (check->type && check->type != type) | |
212 | continue; | |
213 | ||
214 | // Check path | |
0c931ee9 MT |
215 | r = pakfire_path_match(check->path, path); |
216 | switch (r) { | |
217 | // No match | |
218 | case 0: | |
219 | continue; | |
220 | ||
221 | // Match! | |
222 | case 1: | |
223 | DEBUG(pakfire, "%s matches check '%s'\n", path, check->path); | |
224 | ||
225 | return check; | |
226 | ||
227 | // Error :( | |
228 | default: | |
229 | goto ERROR; | |
230 | } | |
231 | } | |
232 | ||
233 | ERROR: | |
234 | ERROR(pakfire, "Could not find FHS entry for %s: %m\n", path); | |
235 | ||
236 | return NULL; | |
237 | } | |
238 | ||
a2f877ba MT |
239 | static int pakfire_fhs_check_world_writable( |
240 | struct pakfire* pakfire, struct pakfire_file* file) { | |
15c1f85e MT |
241 | // Run this check only for regular files |
242 | switch (pakfire_file_get_type(file)) { | |
243 | case S_IFREG: | |
244 | break; | |
245 | ||
246 | default: | |
247 | return 0; | |
248 | } | |
249 | ||
a2f877ba MT |
250 | // Fetch path |
251 | const char* path = pakfire_file_get_path(file); | |
252 | ||
253 | // Fetch permissions | |
254 | const mode_t perms = pakfire_file_get_perms(file); | |
255 | ||
256 | // Check that none of the executable bits are set | |
257 | if ((perms & (S_IWUSR|S_IWGRP|S_IWOTH)) == (S_IWUSR|S_IWGRP|S_IWOTH)) { | |
258 | DEBUG(pakfire, "%s is world-writable\n", path); | |
259 | return 1; | |
260 | } | |
261 | ||
262 | return 0; | |
263 | } | |
264 | ||
84a9a7b7 | 265 | static int pakfire_fhs_check_perms(struct pakfire* pakfire, |
0c931ee9 | 266 | const struct pakfire_fhs_check* check, struct pakfire_file* file) { |
84a9a7b7 MT |
267 | // No permissions defined. Skipping check... |
268 | if (!check->perms) | |
0c931ee9 MT |
269 | return 0; |
270 | ||
271 | const char* path = pakfire_file_get_path(file); | |
272 | ||
84a9a7b7 MT |
273 | // Fetch perms |
274 | const mode_t perms = pakfire_file_get_perms(file); | |
0c931ee9 | 275 | |
84a9a7b7 MT |
276 | // Check if they match |
277 | if (check->perms != perms) { | |
278 | DEBUG(pakfire, "%s: Permissions do not match\n", path); | |
279 | return 1; | |
0c931ee9 MT |
280 | } |
281 | ||
282 | // Check passed | |
283 | return 0; | |
284 | } | |
285 | ||
26f2d651 MT |
286 | static int pakfire_fhs_check_ownership(struct pakfire* pakfire, |
287 | const struct pakfire_fhs_check* check, struct pakfire_file* file) { | |
288 | const char* path = pakfire_file_get_path(file); | |
289 | ||
290 | // Check uname | |
291 | if (check->uname) { | |
292 | const char* uname = pakfire_file_get_uname(file); | |
293 | if (!uname) | |
294 | return 1; | |
295 | ||
296 | if (strcmp(check->uname, uname) != 0) { | |
297 | DEBUG(pakfire, "%s: uname does not match\n", path); | |
298 | return 1; | |
299 | } | |
300 | } | |
301 | ||
302 | // Check gname | |
303 | if (check->gname) { | |
304 | const char* gname = pakfire_file_get_gname(file); | |
305 | if (!gname) | |
306 | return 1; | |
307 | ||
308 | if (strcmp(check->gname, gname) != 0) { | |
309 | DEBUG(pakfire, "%s: gname does not match\n", path); | |
310 | return 1; | |
311 | } | |
312 | } | |
313 | ||
314 | // Pass | |
315 | return 0; | |
316 | } | |
317 | ||
760e1615 MT |
318 | static int pakfire_fhs_check_noexec(struct pakfire* pakfire, |
319 | const struct pakfire_fhs_check* check, struct pakfire_file* file) { | |
320 | // Skip this check if PAKFIRE_FHS_NOEXEC is not set | |
321 | if (!(check->flags & PAKFIRE_FHS_NOEXEC)) | |
322 | return 0; | |
323 | ||
324 | // Fetch path | |
325 | const char* path = pakfire_file_get_path(file); | |
326 | ||
327 | // Fetch permissions | |
328 | const mode_t perms = pakfire_file_get_perms(file); | |
329 | ||
330 | // Check that none of the executable bits are set | |
331 | if (perms & (S_IXUSR|S_IXGRP|S_IXOTH)) { | |
332 | DEBUG(pakfire, "%s must not be executable\n", path); | |
333 | return 1; | |
334 | } | |
335 | ||
336 | return 0; | |
337 | } | |
338 | ||
0c931ee9 MT |
339 | int pakfire_fhs_check_file(struct pakfire* pakfire, struct pakfire_file* file) { |
340 | const struct pakfire_fhs_check* check = NULL; | |
341 | int r; | |
342 | ||
343 | // Get the file path | |
344 | const char* path = pakfire_file_get_path(file); | |
345 | if (!path) | |
346 | return 1; | |
347 | ||
a2f877ba MT |
348 | // Check for world-writable permissions |
349 | r = pakfire_fhs_check_world_writable(pakfire, file); | |
350 | if (r) | |
351 | return r; | |
352 | ||
0c931ee9 | 353 | // Find a check |
84a9a7b7 | 354 | check = pakfire_fhs_find_check(pakfire, file); |
0c931ee9 MT |
355 | if (!check) { |
356 | ERROR(pakfire, "Could not match file %s: %m\n", path); | |
357 | return 1; | |
358 | } | |
359 | ||
360 | // Should this file exist at all? | |
361 | if (check->flags & PAKFIRE_FHS_MUSTNOTEXIST) { | |
225accad | 362 | DEBUG(pakfire, "%s must not exist here\n", path); |
0c931ee9 MT |
363 | return 1; |
364 | } | |
365 | ||
84a9a7b7 MT |
366 | // Check permissions |
367 | r = pakfire_fhs_check_perms(pakfire, check, file); | |
0c931ee9 MT |
368 | if (r) |
369 | return r; | |
370 | ||
26f2d651 MT |
371 | // Check ownership |
372 | r = pakfire_fhs_check_ownership(pakfire, check, file); | |
373 | if (r) | |
374 | return r; | |
375 | ||
760e1615 MT |
376 | // Check for PAKFIRE_FHS_NOEXEC |
377 | r = pakfire_fhs_check_noexec(pakfire, check, file); | |
378 | if (r) | |
379 | return r; | |
380 | ||
0c931ee9 MT |
381 | // Check passed! |
382 | return 0; | |
383 | } |