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 {
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(
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())?,
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;
+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;
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;
* 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())
}
}
}
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()?)?;
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,
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()?)?;
.await?)
}
-
pub async fn q_quotaon(
msg: &ProxyMessageBuffer,
cmd: c_int,
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()?)?;
})
.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?)
+}