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