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