33use std:: env;
44use std:: ffi:: CString ;
55use std:: fs:: { self , OpenOptions } ;
6+ use std:: os:: unix:: fs:: FileTypeExt ;
7+ use std:: os:: unix:: fs:: MetadataExt ;
68use std:: os:: unix:: fs:: { OpenOptionsExt , PermissionsExt } ;
79use std:: path:: Path ;
810
911use anyhow:: { Context , Result , bail} ;
1012
11- use crate :: manifest:: { Manifest , Mount } ;
13+ use crate :: manifest:: { Device , Manifest , Mount } ;
1214
1315use super :: util:: { c_path, syscall_ok} ;
1416
@@ -49,6 +51,50 @@ pub(super) fn mount_resources(manifest: &Manifest) -> Result<()> {
4951 Ok ( ( ) )
5052}
5153
54+ #[ derive( Debug ) ]
55+ pub ( super ) struct ResolvedDevice {
56+ destination : std:: path:: PathBuf ,
57+ major : u32 ,
58+ minor : u32 ,
59+ }
60+
61+ /// Resolve the device identity while the host `/dev` tree is still visible.
62+ /// Manifest validation has already constrained every path to canonical VFIO
63+ /// names; this second check proves each selected entry is a real character
64+ /// device rather than trusting a regular file at an allowed-looking path.
65+ pub ( super ) fn resolve_devices ( manifest : & Manifest ) -> Result < Vec < ResolvedDevice > > {
66+ manifest
67+ . devices
68+ . iter ( )
69+ . map ( resolve_device)
70+ . collect :: < Result < Vec < _ > > > ( )
71+ }
72+
73+ fn resolve_device ( device : & Device ) -> Result < ResolvedDevice > {
74+ let metadata = fs:: symlink_metadata ( & device. source )
75+ . with_context ( || format ! ( "stat device source {}" , device. source. display( ) ) ) ?;
76+ if metadata. file_type ( ) . is_symlink ( ) || !metadata. file_type ( ) . is_char_device ( ) {
77+ bail ! (
78+ "device source is not a character device: {}" ,
79+ device. source. display( )
80+ ) ;
81+ }
82+ let canonical = fs:: canonicalize ( & device. source )
83+ . with_context ( || format ! ( "canonicalize device source {}" , device. source. display( ) ) ) ?;
84+ if canonical != device. source {
85+ bail ! (
86+ "device source changed during resolution: {}" ,
87+ device. source. display( )
88+ ) ;
89+ }
90+ let device_id = metadata. rdev ( ) ;
91+ Ok ( ResolvedDevice {
92+ destination : device. destination . 0 . clone ( ) ,
93+ major : libc:: major ( device_id) as u32 ,
94+ minor : libc:: minor ( device_id) as u32 ,
95+ } )
96+ }
97+
5298pub ( super ) fn pivot_into_jail ( root : & Path ) -> Result < ( ) > {
5399 mount_call ( Some ( root) , root, libc:: MS_BIND | libc:: MS_REC ) ?;
54100 env:: set_current_dir ( root) . context ( "enter jail root" ) ?;
@@ -66,14 +112,24 @@ pub(super) fn pivot_into_jail(root: &Path) -> Result<()> {
66112 syscall_ok ( unsafe { libc:: rmdir ( old_root. as_ptr ( ) ) } ) . context ( "remove old root" )
67113}
68114
69- pub ( super ) fn create_device_nodes ( uid : u32 , gid : u32 ) -> Result < ( ) > {
115+ pub ( super ) fn create_device_nodes ( uid : u32 , gid : u32 , devices : & [ ResolvedDevice ] ) -> Result < ( ) > {
70116 fs:: create_dir_all ( "/dev/net" ) . context ( "create jailed dev directory" ) ?;
117+ if !devices. is_empty ( ) {
118+ fs:: create_dir_all ( "/dev/vfio" ) . context ( "create jailed VFIO directory" ) ?;
119+ }
71120 create_character_device ( Path :: new ( "/dev/kvm" ) , 10 , 232 ) ?;
72121 create_character_device ( Path :: new ( "/dev/net/tun" ) , 10 , 200 ) ?;
73122 // Cloud Hypervisor's default virtio-rng device reads from /dev/urandom.
74123 // Expose only this non-blocking entropy device; guest workloads never
75124 // receive the host /dev filesystem.
76125 create_character_device ( Path :: new ( "/dev/urandom" ) , 1 , 9 ) ?;
126+ for device in devices {
127+ let destination = Path :: new ( "/" ) . join ( & device. destination ) ;
128+ create_character_device ( & destination, device. major , device. minor )
129+ . with_context ( || format ! ( "create jailed device {}" , destination. display( ) ) ) ?;
130+ chown_path ( & destination, uid, gid)
131+ . with_context ( || format ! ( "chown jailed device {}" , destination. display( ) ) ) ?;
132+ }
77133 for path in [
78134 Path :: new ( "/" ) ,
79135 Path :: new ( "/dev/kvm" ) ,
0 commit comments