6
6
//! is mounted, with actual `procfs`, and without any additional mount points
7
7
//! on top of the paths we open.
8
8
9
+ use crate :: fd:: { AsFd , BorrowedFd } ;
10
+ use crate :: ffi:: ZStr ;
9
11
use crate :: fs:: {
10
- cwd, fstat, fstatfs, major, openat, renameat, FileType , Mode , OFlags , Stat , PROC_SUPER_MAGIC ,
12
+ cwd, fstat, fstatfs, major, openat, renameat, Dir , FileType , Mode , OFlags , Stat ,
13
+ PROC_SUPER_MAGIC ,
11
14
} ;
12
- use crate :: imp:: fd:: { AsFd , BorrowedFd } ;
13
15
use crate :: io:: { self , OwnedFd } ;
14
16
use crate :: path:: DecInt ;
15
17
use crate :: process:: { getgid, getpid, getuid, Gid , RawGid , RawUid , Uid } ;
@@ -27,6 +29,7 @@ enum Kind {
27
29
Proc ,
28
30
Pid ,
29
31
Fd ,
32
+ File ,
30
33
}
31
34
32
35
/// Check a subdirectory of "/proc" for anomalies.
@@ -56,6 +59,7 @@ fn check_proc_entry_with_stat(
56
59
match kind {
57
60
Kind :: Proc => check_proc_root ( entry, & entry_stat) ?,
58
61
Kind :: Pid | Kind :: Fd => check_proc_subdir ( entry, & entry_stat, proc_stat) ?,
62
+ Kind :: File => check_proc_file ( & entry_stat, proc_stat) ?,
59
63
}
60
64
61
65
// Check the ownership of the directory.
@@ -85,6 +89,13 @@ fn check_proc_entry_with_stat(
85
89
return Err ( io:: Error :: NOTSUP ) ;
86
90
}
87
91
}
92
+ Kind :: File => {
93
+ // Check that files in procfs don't have extraneous hard links to
94
+ // them (which might indicate hard links to other things).
95
+ if entry_stat. st_nlink != 1 {
96
+ return Err ( io:: Error :: NOTSUP ) ;
97
+ }
98
+ }
88
99
}
89
100
90
101
Ok ( entry_stat)
@@ -133,6 +144,17 @@ fn check_proc_subdir(
133
144
Ok ( ( ) )
134
145
}
135
146
147
+ fn check_proc_file ( stat : & Stat , proc_stat : Option < & Stat > ) -> io:: Result < ( ) > {
148
+ // Check that we have a regular file.
149
+ if FileType :: from_raw_mode ( stat. st_mode ) != FileType :: RegularFile {
150
+ return Err ( io:: Error :: NOTSUP ) ;
151
+ }
152
+
153
+ check_proc_nonroot ( stat, proc_stat) ?;
154
+
155
+ Ok ( ( ) )
156
+ }
157
+
136
158
fn check_proc_nonroot ( stat : & Stat , proc_stat : Option < & Stat > ) -> io:: Result < ( ) > {
137
159
// Check that we haven't been linked back to the root of "/proc".
138
160
if stat. st_ino == PROC_ROOT_INO {
@@ -306,3 +328,151 @@ type StaticFd = OnceCell<(OwnedFd, Stat)>;
306
328
fn new_static_fd ( fd : OwnedFd , stat : Stat ) -> ( OwnedFd , Stat ) {
307
329
( fd, stat)
308
330
}
331
+
332
+ /// Returns a handle to Linux's `/proc/self/fdinfo` directory.
333
+ ///
334
+ /// This ensures that `/proc/self/fdinfo` is `procfs`, that nothing is mounted
335
+ /// on top of it, and that it looks normal. It also returns the `Stat` of
336
+ /// `/proc/self/fd`.
337
+ ///
338
+ /// # References
339
+ /// - [Linux]
340
+ ///
341
+ /// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html
342
+ fn proc_self_fdinfo ( ) -> io:: Result < ( BorrowedFd < ' static > , & ' static Stat ) > {
343
+ static PROC_SELF_FDINFO : OnceCell < ( OwnedFd , Stat ) > = OnceCell :: new ( ) ;
344
+
345
+ PROC_SELF_FDINFO
346
+ . get_or_try_init ( || {
347
+ let ( _, proc_stat) = proc ( ) ?;
348
+
349
+ let ( proc_self, proc_self_stat) = proc_self ( ) ?;
350
+ let oflags = OFlags :: NOFOLLOW
351
+ | OFlags :: PATH
352
+ | OFlags :: DIRECTORY
353
+ | OFlags :: CLOEXEC
354
+ | OFlags :: NOCTTY
355
+ | OFlags :: NOATIME ;
356
+
357
+ // Open "/proc/self/fdinfo".
358
+ let proc_self_fdinfo = openat ( & proc_self, zstr ! ( "fdinfo" ) , oflags, Mode :: empty ( ) )
359
+ . map_err ( |_err| io:: Error :: NOTSUP ) ?;
360
+ let proc_self_fdinfo_stat = check_proc_entry (
361
+ Kind :: Fd ,
362
+ proc_self_fdinfo. as_fd ( ) ,
363
+ Some ( proc_stat) ,
364
+ proc_self_stat. st_uid ,
365
+ proc_self_stat. st_gid ,
366
+ )
367
+ . map_err ( |_err| io:: Error :: NOTSUP ) ?;
368
+
369
+ Ok ( ( proc_self_fdinfo, proc_self_fdinfo_stat) )
370
+ } )
371
+ . map ( |( owned, stat) | ( owned. as_fd ( ) , stat) )
372
+ }
373
+
374
+ /// Returns a handle to a Linux `/proc/self/fdinfo/<fd>` file.
375
+ ///
376
+ /// This ensures that `/proc/self/fdinfo/<fd>` is `procfs`, that nothing is
377
+ /// mounted on top of it, and that it looks normal.
378
+ ///
379
+ /// # References
380
+ /// - [Linux]
381
+ ///
382
+ /// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html
383
+ #[ inline]
384
+ pub fn proc_self_fdinfo_fd < Fd : AsFd > ( fd : & Fd ) -> io:: Result < OwnedFd > {
385
+ let fd = fd. as_fd ( ) ;
386
+ _proc_self_fdinfo ( fd)
387
+ }
388
+
389
+ fn _proc_self_fdinfo ( fd : BorrowedFd < ' _ > ) -> io:: Result < OwnedFd > {
390
+ let ( proc_self_fdinfo, proc_self_fdinfo_stat) = proc_self_fdinfo ( ) ?;
391
+ let fd_str = DecInt :: from_fd ( & fd) ;
392
+ open_and_check_file ( proc_self_fdinfo, proc_self_fdinfo_stat, fd_str. as_z_str ( ) )
393
+ }
394
+
395
+ /// Returns a handle to a Linux `/proc/self/pagemap` file.
396
+ ///
397
+ /// This ensures that `/proc/self/pagemap` is `procfs`, that nothing is
398
+ /// mounted on top of it, and that it looks normal.
399
+ ///
400
+ /// # References
401
+ /// - [Linux]
402
+ /// - [Linux pagemap]
403
+ ///
404
+ /// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html
405
+ /// [Linux pagemap]: https://www.kernel.org/doc/Documentation/vm/pagemap.txt
406
+ #[ inline]
407
+ pub fn proc_self_pagemap ( ) -> io:: Result < OwnedFd > {
408
+ proc_self_file ( zstr ! ( "pagemap" ) )
409
+ }
410
+
411
+ /// Returns a handle to a Linux `/proc/self/maps` file.
412
+ ///
413
+ /// This ensures that `/proc/self/maps` is `procfs`, that nothing is
414
+ /// mounted on top of it, and that it looks normal.
415
+ ///
416
+ /// # References
417
+ /// - [Linux]
418
+ ///
419
+ /// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html
420
+ #[ inline]
421
+ pub fn proc_self_maps ( ) -> io:: Result < OwnedFd > {
422
+ proc_self_file ( zstr ! ( "maps" ) )
423
+ }
424
+
425
+ /// Open a file under `/proc/self`.
426
+ fn proc_self_file ( name : & ZStr ) -> io:: Result < OwnedFd > {
427
+ let ( proc_self, proc_self_stat) = proc_self ( ) ?;
428
+ open_and_check_file ( proc_self, proc_self_stat, name)
429
+ }
430
+
431
+ /// Open a procfs file within in `dir` and check it for bind mounts.
432
+ fn open_and_check_file ( dir : BorrowedFd , dir_stat : & Stat , name : & ZStr ) -> io:: Result < OwnedFd > {
433
+ let ( _, proc_stat) = proc ( ) ?;
434
+
435
+ let oflags =
436
+ OFlags :: RDONLY | OFlags :: CLOEXEC | OFlags :: NOFOLLOW | OFlags :: NOCTTY | OFlags :: NOATIME ;
437
+ let file = openat ( & dir, name, oflags, Mode :: empty ( ) ) . map_err ( |_err| io:: Error :: NOTSUP ) ?;
438
+ let file_stat = fstat ( & file) ?;
439
+
440
+ // Open a copy of the `dir` handle so that we can read from it
441
+ // without modifying the current position of the original handle.
442
+ let dot = openat ( & dir, zstr ! ( "." ) , oflags | OFlags :: DIRECTORY , Mode :: empty ( ) )
443
+ . map_err ( |_err| io:: Error :: NOTSUP ) ?;
444
+
445
+ // Confirm that we got the same inode.
446
+ let dot_stat = fstat ( & dot) . map_err ( |_err| io:: Error :: NOTSUP ) ?;
447
+ if ( dot_stat. st_dev , dot_stat. st_ino ) != ( dir_stat. st_dev , dir_stat. st_ino ) {
448
+ return Err ( io:: Error :: NOTSUP ) ;
449
+ }
450
+
451
+ // `is_mountpoint` only works on directory mount points, not file mount
452
+ // points. To detect file mount points, scan the parent directory to see
453
+ // if we can find a regular file with an inode and name that matches the
454
+ // file we just opened. If we can't find it, there could be a file bind
455
+ // mount on top of the file we want.
456
+ let dir = Dir :: from ( dot) . map_err ( |_err| io:: Error :: NOTSUP ) ?;
457
+ for entry in dir {
458
+ let entry = entry. map_err ( |_err| io:: Error :: NOTSUP ) ?;
459
+ if entry. ino ( ) == file_stat. st_ino
460
+ && entry. file_type ( ) == FileType :: RegularFile
461
+ && entry. file_name ( ) == name
462
+ {
463
+ // Ok, we found it. Proceed to check the file handle and succeed.
464
+ let _ = check_proc_entry_with_stat (
465
+ Kind :: File ,
466
+ file. as_fd ( ) ,
467
+ file_stat,
468
+ Some ( proc_stat) ,
469
+ dir_stat. st_uid ,
470
+ dir_stat. st_gid ,
471
+ ) ?;
472
+
473
+ return Ok ( file) ;
474
+ }
475
+ }
476
+
477
+ Err ( io:: Error :: NOTSUP )
478
+ }
0 commit comments