]> git.proxmox.com Git - pve-lxc-syscalld.git/commitdiff
implement remaining quotactl calls
authorWolfgang Bumiller <w.bumiller@proxmox.com>
Wed, 17 Jul 2019 08:23:42 +0000 (10:23 +0200)
committerWolfgang Bumiller <w.bumiller@proxmox.com>
Wed, 17 Jul 2019 08:23:42 +0000 (10:23 +0200)
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
src/lxcseccomp.rs
src/pidfd.rs
src/sys_quotactl.rs

index 7c3711bef446e351698aad162480d8bd5aa1a561..39b0bc410456bfbb5071178fa0c8274542443998 100644 (file)
@@ -107,9 +107,15 @@ impl ProxyMessageBuffer {
         }
     }
 
+    fn reset(&mut self) {
+        self.proxy_msg.cookie_len = 0;
+        self.mem_fd = None;
+        self.pid_fd = None;
+    }
+
     /// Returns None on EOF.
     pub async fn recv(&mut self, socket: &AsyncSeqPacketSocket) -> Result<bool, Error> {
-        self.proxy_msg.cookie_len = 0;
+        self.reset();
 
         unsafe {
             self.cookie_buf.set_len(self.cookie_buf.capacity());
@@ -167,11 +173,6 @@ impl ProxyMessageBuffer {
         self.mem_fd.as_ref().unwrap()
     }
 
-    pub fn drop_fds(&mut self) {
-        self.pid_fd = None;
-        self.mem_fd = None;
-    }
-
     /// Send the current data as response.
     pub async fn respond(&mut self, socket: &AsyncSeqPacketSocket) -> io::Result<()> {
         let iov = [
@@ -319,7 +320,7 @@ impl ProxyMessageBuffer {
 
     /// Read a user space pointer parameter.
     #[inline]
-    pub fn arg_struct<T>(&self, arg: u32) -> Result<T, Error> {
+    pub fn arg_struct_by_ptr<T>(&self, arg: u32) -> Result<T, Error> {
         let offset = self.arg(arg)?;
         let mut data: T = unsafe { mem::zeroed() };
         let slice = unsafe {
index 65ef2739ee2f38b6366386c15fb368158652d1db..4df10bf06d93106b616e4d1d69863806829e4678 100644 (file)
@@ -44,6 +44,36 @@ pub struct ProcStatus {
     umask: libc::mode_t,
 }
 
+pub struct IdMapEntry {
+    ns: u64,
+    host: u64,
+    range: u64,
+}
+
+pub struct IdMap(Vec<IdMapEntry>);
+
+impl IdMap {
+    pub fn map_into(&self, id: u64) -> Option<u64> {
+        for entry in self.0.iter() {
+            if entry.host <= id && entry.host + entry.range > id {
+                return Some(entry.ns + id - entry.host);
+            }
+        }
+
+        None
+    }
+
+    pub fn map_from(&self, id: u64) -> Option<u64> {
+        for entry in self.0.iter() {
+            if entry.ns <= id && entry.ns + entry.range > id {
+                return Some(id + entry.host);
+            }
+        }
+
+        None
+    }
+}
+
 impl PidFd {
     pub fn current() -> io::Result<Self> {
         let fd = c_try!(unsafe {
@@ -171,18 +201,18 @@ impl PidFd {
         Err(io::ErrorKind::NotFound.into())
     }
 
+    #[inline]
+    fn __check_uid_gid(value: Option<&str>) -> io::Result<libc::uid_t> {
+        value
+            .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "bad 'Uid/Gid:' line in proc"))?
+            .parse::<libc::uid_t>()
+            .map_err(|_| io::Error::new(io::ErrorKind::Other, "failed to parse uid from proc"))
+    }
+
     pub fn get_status(&self) -> io::Result<ProcStatus> {
         let reader =
             self.open_buffered(unsafe { CStr::from_bytes_with_nul_unchecked(b"status\0") })?;
 
-        #[inline]
-        fn check_uid_gid(value: Option<&str>) -> io::Result<libc::uid_t> {
-            value
-                .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "bad 'Uid/Gid:' line in proc"))?
-                .parse::<libc::uid_t>()
-                .map_err(|_| io::Error::new(io::ErrorKind::Other, "failed to parse uid from proc"))
-        }
-
         #[inline]
         fn check_u64_hex(value: Option<&str>) -> io::Result<u64> {
             Ok(u64::from_str_radix(
@@ -213,16 +243,16 @@ impl PidFd {
             let mut parts = line.split_ascii_whitespace();
             match parts.next() {
                 Some("Uid:") => {
-                    ids.ruid = check_uid_gid(parts.next())?;
-                    ids.euid = check_uid_gid(parts.next())?;
-                    ids.suid = check_uid_gid(parts.next())?;
-                    ids.fsuid = check_uid_gid(parts.next())?;
+                    ids.ruid = Self::__check_uid_gid(parts.next())?;
+                    ids.euid = Self::__check_uid_gid(parts.next())?;
+                    ids.suid = Self::__check_uid_gid(parts.next())?;
+                    ids.fsuid = Self::__check_uid_gid(parts.next())?;
                 }
                 Some("Gid:") => {
-                    ids.rgid = check_uid_gid(parts.next())?;
-                    ids.egid = check_uid_gid(parts.next())?;
-                    ids.sgid = check_uid_gid(parts.next())?;
-                    ids.fsgid = check_uid_gid(parts.next())?;
+                    ids.rgid = Self::__check_uid_gid(parts.next())?;
+                    ids.egid = Self::__check_uid_gid(parts.next())?;
+                    ids.sgid = Self::__check_uid_gid(parts.next())?;
+                    ids.fsgid = Self::__check_uid_gid(parts.next())?;
                 }
                 Some("CapInh:") => caps.inheritable = check_u64_hex(parts.next())?,
                 Some("CapPrm:") => caps.permitted = check_u64_hex(parts.next())?,
@@ -271,6 +301,30 @@ impl PidFd {
         Ok(cgroups)
     }
 
+    pub fn get_uid_gid_map(&self, file: &CStr) -> Result<IdMap, Error> {
+        let reader = self.open_buffered(file)?;
+
+        let mut entries = Vec::new();
+        for line in reader.lines() {
+            let line = line?;
+            let mut parts = line.split_ascii_whitespace();
+            let ns = Self::__check_uid_gid(parts.next())? as u64;
+            let host = Self::__check_uid_gid(parts.next())? as u64;
+            let range = Self::__check_uid_gid(parts.next())? as u64;
+            entries.push(IdMapEntry { ns, host, range });
+        }
+
+        Ok(IdMap(entries))
+    }
+
+    pub fn get_uid_map(&self) -> Result<IdMap, Error> {
+        self.get_uid_gid_map(unsafe { CStr::from_bytes_with_nul_unchecked(b"uid_map\0") })
+    }
+
+    pub fn get_gid_map(&self) -> Result<IdMap, Error> {
+        self.get_uid_gid_map(unsafe { CStr::from_bytes_with_nul_unchecked(b"gid_map\0") })
+    }
+
     pub fn read_file(&self, file: &CStr) -> io::Result<Vec<u8>> {
         use io::Read;
 
index ac355ccf0853037604b1c956fd94e6985e620bf2..2b0032ca27df400a898fbde548140b28c95caa0c 100644 (file)
@@ -1,5 +1,6 @@
+use std::convert::TryFrom;
 use std::ffi::CString;
-use std::{mem, ptr};
+use std::{io, mem, ptr};
 use std::os::raw::{c_int, c_uint};
 
 use failure::Error;
@@ -7,7 +8,7 @@ use nix::errno::Errno;
 
 use crate::fork::forking_syscall;
 use crate::lxcseccomp::ProxyMessageBuffer;
-use crate::pidfd::PidFd;
+use crate::pidfd::{IdMap, PidFd};
 use crate::sc_libc_try;
 use crate::syscall::SyscallStatus;
 
@@ -24,44 +25,64 @@ use crate::syscall::SyscallStatus;
  *  QCMD(SubCmd, Type)
  *      Type: USRQUOTA | GRPQUOTA | PRJQUOTA (but we don't need to care)
  * SubCmd:
- *  |          name           addr meaning|
- *        Q_QUOTAON     path to quota file
- *       Q_QUOTAOFF                ignored
- *       Q_GETQUOTA        struct dqblk {}       a page should be sufficient
- *   Q_GETNEXTQUOTA    struct nextdqblk {}       a page should be sufficient
- *       Q_SETQUOTA        struct dqblk {}       a page should be sufficient
- *       Q_SETQUOTA        struct dqblk {}       a page should be sufficient
- *        Q_SETQLIM                              -EOPNOTSUPP: not documented anymore
- *         Q_SETUSE                              -EOPNOTSUPP: not documented anymore
- *        Q_GETINFO       struct dqinfo {}
- *        Q_SETINFO       struct dqinfo {}
- *         Q_GETFMT                [u8; 4]
- *           Q_SYNC                ignored       -EOPNOTSUPP if `special` is NULL!
- *       Q_GETSTATS      struct dqstats {}       -EOPNOTSUPP: obsolete, removed since 2.4.22!
+ *  |Done?         name           addr meaning|
+ *    X       Q_QUOTAON     path to quota file
+ *    X      Q_QUOTAOFF                ignored
+ *    X      Q_GETQUOTA        struct dqblk {}
+ *    X      Q_SETQUOTA        struct dqblk {}
+ *    X       Q_GETINFO       struct dqinfo {}
+ *    X        Q_GETFMT                [u8; 4]
+ *    X       Q_SETQLIM                              -EOPNOTSUPP: not documented anymore
+ *    X        Q_SETUSE                              -EOPNOTSUPP: not documented anymore
+ *    X      Q_GETSTATS      struct dqstats {}       -EOPNOTSUPP: obsolete, removed since 2.4.22!
+ *    X  Q_GETNEXTQUOTA    struct nextdqblk {}
+ *    X       Q_SETINFO       struct dqinfo {}
+ *    X          Q_SYNC                ignored       -EOPNOTSUPP if `special` is NULL!
  *
  * xfs stuff:
- *       Q_XQUOTAON           unsigned int
- *      Q_XQUOTAOFF           unsigned int
- *      ...
- *      (we don't actually have xfs containers atm...)
+ *           Q_XQUOTAON           unsigned int
+ *          Q_XQUOTAOFF           unsigned int
+ *          ...
+ *          (we don't actually have xfs containers atm...)
  */
 
+const Q_GETNEXTQUOTA: c_int = 0x800009;
+
+const KINDMASK: c_int = 0xff;
 const SUBCMDSHIFT: c_int = 8;
 
+#[repr(C)]
+struct nextdqblk {
+    dqblk: libc::dqblk,
+    dqb_id: u32,
+}
+
 pub async fn quotactl(msg: &ProxyMessageBuffer) -> Result<SyscallStatus, Error> {
     let cmd = msg.arg_int(0)?;
     let special = msg.arg_opt_c_string(1)?;
     // let _id = msg.arg_int(2)?;
     // let _addr = msg.arg_caddr_t(3)?;
 
+    // FIXME: We can *generally* check that `special` if not None points to a block device owned
+    // by the container. On the other hand, the container should not have access to the device
+    // anyway unless the `devices` cgroup allows it, and should not have been allowed to `mknod` a
+    // device on a non-NODEV mounted file system.
+
+    let kind = cmd & KINDMASK;
     let subcmd = ((cmd as c_uint) >> SUBCMDSHIFT) as c_int;
     match subcmd {
         libc::Q_GETINFO => q_getinfo(msg, cmd, special).await,
+        libc::Q_SETINFO => q_setinfo(msg, cmd, special).await,
         libc::Q_GETFMT => q_getfmt(msg, cmd, special).await,
         libc::Q_QUOTAON => q_quotaon(msg, cmd, special).await,
+        libc::Q_QUOTAOFF => q_quotaoff(msg, cmd, special).await,
+        libc::Q_GETQUOTA => q_getquota(msg, cmd, special, kind).await,
+        libc::Q_SETQUOTA => q_setquota(msg, cmd, special, kind).await,
+        libc::Q_SYNC => q_sync(msg, cmd, special).await,
+        Q_GETNEXTQUOTA => q_getnextquota(msg, cmd, special, kind).await,
         _ => {
-            eprintln!("Unhandled quota subcommand: {}", subcmd);
-            Ok(Errno::ENOSYS.into())
+            //eprintln!("Unhandled quota subcommand: {:x}", subcmd);
+            Ok(Errno::EOPNOTSUPP.into())
         }
     }
 }
@@ -83,7 +104,7 @@ pub async fn q_getinfo(
     let id = msg.arg_int(2)?;
     let addr = msg.arg_caddr_t(3)? as u64;
 
-    let mut caps = msg.pid_fd().user_caps()?;
+    let caps = msg.pid_fd().user_caps()?;
     Ok(forking_syscall(move || {
         caps.apply(&PidFd::current()?)?;
 
@@ -92,12 +113,38 @@ pub async fn q_getinfo(
         sc_libc_try!(unsafe {
             libc::quotactl(cmd, special, id, &mut data as *mut dqinfo as *mut i8)
         });
+
         msg.mem_write_struct(addr, &data)?;
         Ok(SyscallStatus::Ok(0))
     })
     .await?)
 }
 
+pub async fn q_setinfo(
+    msg: &ProxyMessageBuffer,
+    cmd: c_int,
+    special: Option<CString>,
+) -> Result<SyscallStatus, Error> {
+    let special = match special {
+        Some(s) => s,
+        None => return Ok(Errno::EINVAL.into()),
+    };
+    let id = msg.arg_int(2)?;
+    let mut data: dqinfo = msg.arg_struct_by_ptr(3)?;
+
+    let caps = msg.pid_fd().user_caps()?;
+    Ok(forking_syscall(move || {
+        caps.apply(&PidFd::current()?)?;
+
+        sc_libc_try!(unsafe {
+            libc::quotactl(cmd, special.as_ptr(), id, &mut data as *mut dqinfo as *mut i8)
+        });
+
+        Ok(SyscallStatus::Ok(0))
+    })
+    .await?)
+}
+
 pub async fn q_getfmt(
     msg: &ProxyMessageBuffer,
     cmd: c_int,
@@ -106,7 +153,7 @@ pub async fn q_getfmt(
     let id = msg.arg_int(2)?;
     let addr = msg.arg_caddr_t(3)? as u64;
 
-    let mut caps = msg.pid_fd().user_caps()?;
+    let caps = msg.pid_fd().user_caps()?;
     Ok(forking_syscall(move || {
         caps.apply(&PidFd::current()?)?;
 
@@ -122,7 +169,6 @@ pub async fn q_getfmt(
     .await?)
 }
 
-
 pub async fn q_quotaon(
     msg: &ProxyMessageBuffer,
     cmd: c_int,
@@ -131,7 +177,7 @@ pub async fn q_quotaon(
     let id = msg.arg_int(2)?;
     let addr = msg.arg_c_string(3)?;
 
-    let mut caps = msg.pid_fd().user_caps()?;
+    let caps = msg.pid_fd().user_caps()?;
     Ok(forking_syscall(move || {
         caps.apply(&PidFd::current()?)?;
 
@@ -142,3 +188,157 @@ pub async fn q_quotaon(
     })
     .await?)
 }
+
+pub async fn q_quotaoff(
+    msg: &ProxyMessageBuffer,
+    cmd: c_int,
+    special: Option<CString>,
+) -> Result<SyscallStatus, Error> {
+    let id = msg.arg_int(2)?;
+
+    let caps = msg.pid_fd().user_caps()?;
+    Ok(forking_syscall(move || {
+        caps.apply(&PidFd::current()?)?;
+
+        let special = special.as_ref().map(|c| c.as_ptr()).unwrap_or(ptr::null());
+        let out = sc_libc_try!(unsafe { libc::quotactl(cmd, special, id, ptr::null_mut()) });
+
+        Ok(SyscallStatus::Ok(out.into()))
+    })
+    .await?)
+}
+
+fn uid_gid_arg(
+    msg: &ProxyMessageBuffer,
+    arg: u32,
+    kind: c_int,
+) -> Result<(c_int, Option<IdMap>), Error> {
+    let id = msg.arg_int(arg)?;
+    let map = match kind {
+        libc::USRQUOTA => msg.pid_fd().get_uid_map()?,
+        libc::GRPQUOTA => msg.pid_fd().get_gid_map()?,
+        _ => return Ok((id, None)),
+    };
+
+    let id = map
+        .map_from(id as u64)
+        .ok_or_else(|| Error::from(Errno::ERANGE))?;
+    let id = c_int::try_from(id)
+        .map_err(|_| Error::from(Errno::ERANGE))?;
+
+    Ok((id, Some(map)))
+}
+
+pub async fn q_getquota(
+    msg: &ProxyMessageBuffer,
+    cmd: c_int,
+    special: Option<CString>,
+    kind: c_int,
+) -> Result<SyscallStatus, Error> {
+    let special = match special {
+        Some(s) => s,
+        None => return Ok(Errno::EINVAL.into()),
+    };
+
+    let (id, _) = uid_gid_arg(msg, 2, kind)?;
+    let addr = msg.arg_caddr_t(3)? as u64;
+
+    let caps = msg.pid_fd().user_caps()?;
+    Ok(forking_syscall(move || {
+        caps.apply(&PidFd::current()?)?;
+
+        let mut data: libc::dqblk = unsafe { mem::zeroed() };
+        sc_libc_try!(unsafe {
+            libc::quotactl(cmd, special.as_ptr(), id, &mut data as *mut libc::dqblk as *mut i8)
+        });
+
+        msg.mem_write_struct(addr, &data)?;
+        Ok(SyscallStatus::Ok(0))
+    })
+    .await?)
+}
+
+pub async fn q_setquota(
+    msg: &ProxyMessageBuffer,
+    cmd: c_int,
+    special: Option<CString>,
+    kind: c_int,
+) -> Result<SyscallStatus, Error> {
+    let special = match special {
+        Some(s) => s,
+        None => return Ok(Errno::EINVAL.into()),
+    };
+
+    let (id, _) = uid_gid_arg(msg, 2, kind)?;
+    let mut data: libc::dqblk = msg.arg_struct_by_ptr(3)?;
+
+    let caps = msg.pid_fd().user_caps()?;
+    Ok(forking_syscall(move || {
+        caps.apply(&PidFd::current()?)?;
+
+        sc_libc_try!(unsafe {
+            libc::quotactl(cmd, special.as_ptr(), id, &mut data as *mut libc::dqblk as *mut i8)
+        });
+
+        Ok(SyscallStatus::Ok(0))
+    })
+    .await?)
+}
+
+pub async fn q_getnextquota(
+    msg: &ProxyMessageBuffer,
+    cmd: c_int,
+    special: Option<CString>,
+    kind: c_int,
+) -> Result<SyscallStatus, Error> {
+    let special = match special {
+        Some(s) => s,
+        None => return Ok(Errno::EINVAL.into()),
+    };
+
+    let (id, idmap) = uid_gid_arg(msg, 2, kind)?;
+    let addr = msg.arg_caddr_t(3)? as u64;
+
+    let caps = msg.pid_fd().user_caps()?;
+    Ok(forking_syscall(move || {
+        caps.apply(&PidFd::current()?)?;
+
+        let mut data: nextdqblk = unsafe { mem::zeroed() };
+        sc_libc_try!(unsafe {
+            libc::quotactl(cmd, special.as_ptr(), id, &mut data as *mut nextdqblk as *mut i8)
+        });
+
+        if let Some(idmap) = idmap {
+            data.dqb_id = idmap
+                .map_into(u64::from(data.dqb_id))
+                .ok_or_else(|| io::Error::from_raw_os_error(libc::ERANGE))? as u32;
+        }
+
+        msg.mem_write_struct(addr, &data)?;
+        Ok(SyscallStatus::Ok(0))
+    })
+    .await?)
+}
+
+pub async fn q_sync(
+    msg: &ProxyMessageBuffer,
+    cmd: c_int,
+    special: Option<CString>,
+) -> Result<SyscallStatus, Error> {
+    let special = match special {
+        Some(s) => s,
+        None => return Ok(Errno::EINVAL.into()),
+    };
+
+    let caps = msg.pid_fd().user_caps()?;
+    Ok(forking_syscall(move || {
+        caps.apply(&PidFd::current()?)?;
+
+        sc_libc_try!(unsafe {
+            libc::quotactl(cmd, special.as_ptr(), 0, ptr::null_mut())
+        });
+
+        Ok(SyscallStatus::Ok(0))
+    })
+    .await?)
+}