@@ -70,6 +70,31 @@ pub(super) fn resolve_devices(manifest: &Manifest) -> Result<Vec<ResolvedDevice>
7070 . collect :: < Result < Vec < _ > > > ( )
7171}
7272
73+ const USERFAULTFD_PATH : & str = "/dev/userfaultfd" ;
74+
75+ /// Resolve the host `/dev/userfaultfd` identity before pivot, if the node
76+ /// has one. Cloud Hypervisor OnDemand restore tries this device first, then
77+ /// the `userfaultfd(2)` syscall. Recreating the character device inside the
78+ /// jail (owned by the unprivileged VMM) is the narrow grant upstream
79+ /// recommends, instead of `vm.unprivileged_userfaultfd=1` for every process.
80+ pub ( super ) fn resolve_userfaultfd ( ) -> Result < Option < ResolvedDevice > > {
81+ match fs:: symlink_metadata ( USERFAULTFD_PATH ) {
82+ Err ( err) if err. kind ( ) == std:: io:: ErrorKind :: NotFound => Ok ( None ) ,
83+ Err ( err) => Err ( err) . context ( "stat /dev/userfaultfd" ) ,
84+ Ok ( metadata) => {
85+ if metadata. file_type ( ) . is_symlink ( ) || !metadata. file_type ( ) . is_char_device ( ) {
86+ bail ! ( "/dev/userfaultfd is not a character device" ) ;
87+ }
88+ let device_id = metadata. rdev ( ) ;
89+ Ok ( Some ( ResolvedDevice {
90+ destination : std:: path:: PathBuf :: from ( "dev/userfaultfd" ) ,
91+ major : libc:: major ( device_id) as u32 ,
92+ minor : libc:: minor ( device_id) as u32 ,
93+ } ) )
94+ }
95+ }
96+ }
97+
7398fn resolve_device ( device : & Device ) -> Result < ResolvedDevice > {
7499 let metadata = fs:: symlink_metadata ( & device. source )
75100 . with_context ( || format ! ( "stat device source {}" , device. source. display( ) ) ) ?;
@@ -112,7 +137,12 @@ pub(super) fn pivot_into_jail(root: &Path) -> Result<()> {
112137 syscall_ok ( unsafe { libc:: rmdir ( old_root. as_ptr ( ) ) } ) . context ( "remove old root" )
113138}
114139
115- pub ( super ) fn create_device_nodes ( uid : u32 , gid : u32 , devices : & [ ResolvedDevice ] ) -> Result < ( ) > {
140+ pub ( super ) fn create_device_nodes (
141+ uid : u32 ,
142+ gid : u32 ,
143+ devices : & [ ResolvedDevice ] ,
144+ userfaultfd : Option < & ResolvedDevice > ,
145+ ) -> Result < ( ) > {
116146 fs:: create_dir_all ( "/dev/net" ) . context ( "create jailed dev directory" ) ?;
117147 if !devices. is_empty ( ) {
118148 fs:: create_dir_all ( "/dev/vfio" ) . context ( "create jailed VFIO directory" ) ?;
@@ -123,6 +153,12 @@ pub(super) fn create_device_nodes(uid: u32, gid: u32, devices: &[ResolvedDevice]
123153 // Expose only this non-blocking entropy device; guest workloads never
124154 // receive the host /dev filesystem.
125155 create_character_device ( Path :: new ( "/dev/urandom" ) , 1 , 9 ) ?;
156+ if let Some ( device) = userfaultfd {
157+ create_character_device ( Path :: new ( "/dev/userfaultfd" ) , device. major , device. minor )
158+ . context ( "create jailed /dev/userfaultfd" ) ?;
159+ chown_path ( Path :: new ( "/dev/userfaultfd" ) , uid, gid)
160+ . context ( "chown jailed /dev/userfaultfd" ) ?;
161+ }
126162 for device in devices {
127163 let destination = Path :: new ( "/" ) . join ( & device. destination ) ;
128164 create_character_device ( & destination, device. major , device. minor )
@@ -280,6 +316,7 @@ fn mount_call(source: Option<&Path>, destination: &Path, flags: libc::c_ulong) -
280316#[ cfg( test) ]
281317mod tests {
282318 use super :: artifact_mode;
319+ use super :: resolve_userfaultfd;
283320
284321 #[ test]
285322 fn mounted_artifact_modes_are_minimally_permissive ( ) {
@@ -288,4 +325,19 @@ mod tests {
288325 assert_eq ! ( artifact_mode( true , true ) , 0o500 ) ;
289326 assert_eq ! ( artifact_mode( true , false ) , 0o700 ) ;
290327 }
328+
329+ #[ test]
330+ fn resolve_userfaultfd_is_optional_when_the_host_has_no_device ( ) {
331+ match resolve_userfaultfd ( ) {
332+ Ok ( None ) => { }
333+ Ok ( Some ( device) ) => {
334+ assert_eq ! (
335+ device. destination,
336+ std:: path:: PathBuf :: from( "dev/userfaultfd" )
337+ ) ;
338+ assert ! ( device. major > 0 || device. minor > 0 ) ;
339+ }
340+ Err ( err) => panic ! ( "resolve_userfaultfd: {err}" ) ,
341+ }
342+ }
291343}
0 commit comments