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