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