Merge pull request #1023 from lifupan/fix_stdio

agent: fixes the permissions of PID 1's STDIO
This commit is contained in:
Peng Tao 2020-10-25 10:21:11 +08:00 committed by GitHub
commit 74a155c65b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -39,6 +39,8 @@ use nix::sched::{self, CloneFlags};
use nix::sys::signal::{self, Signal}; use nix::sys::signal::{self, Signal};
use nix::sys::stat::{self, Mode}; use nix::sys::stat::{self, Mode};
use nix::unistd::{self, ForkResult, Gid, Pid, Uid}; use nix::unistd::{self, ForkResult, Gid, Pid, Uid};
use std::os::unix::fs::MetadataExt;
use std::os::unix::io::AsRawFd;
use protobuf::SingularPtrField; use protobuf::SingularPtrField;
@ -545,6 +547,12 @@ fn do_init_child(cwfd: RawFd) -> Result<()> {
let uid = Uid::from_raw(guser.uid); let uid = Uid::from_raw(guser.uid);
let gid = Gid::from_raw(guser.gid); let gid = Gid::from_raw(guser.gid);
// only change stdio devices owner when user
// isn't root.
if guser.uid != 0 {
set_stdio_permissions(guser.uid)?;
}
setid(uid, gid)?; setid(uid, gid)?;
if !guser.additional_gids.is_empty() { if !guser.additional_gids.is_empty() {
@ -639,6 +647,43 @@ fn do_init_child(cwfd: RawFd) -> Result<()> {
do_exec(&args); do_exec(&args);
} }
// set_stdio_permissions fixes the permissions of PID 1's STDIO
// within the container to the specified user.
// The ownership needs to match because it is created outside of
// the container and needs to be localized.
fn set_stdio_permissions(uid: libc::uid_t) -> Result<()> {
let meta = fs::metadata("/dev/null")?;
let fds = [
std::io::stdin().as_raw_fd(),
std::io::stdout().as_raw_fd(),
std::io::stderr().as_raw_fd(),
];
for fd in &fds {
let stat = stat::fstat(*fd)?;
// Skip chown of /dev/null if it was used as one of the STDIO fds.
if stat.st_rdev == meta.rdev() {
continue;
}
// According to the POSIX specification, -1 is used to indicate that owner and group
// are not to be changed. Since uid_t and gid_t are unsigned types, we have to wrap
// around to get -1.
let gid = (0 as libc::gid_t).wrapping_sub(1);
// We only change the uid owner (as it is possible for the mount to
// prefer a different gid, and there's no reason for us to change it).
// The reason why we don't just leave the default uid=X mount setup is
// that users expect to be able to actually use their console. Without
// this code, you couldn't effectively run as a non-root user inside a
// container and also have a console set up.
let res = unsafe { libc::fchown(*fd, uid, gid) };
Errno::result(res).map_err(|e| anyhow!(e).context("set stdio permissions failed"))?;
}
Ok(())
}
impl BaseContainer for LinuxContainer { impl BaseContainer for LinuxContainer {
fn id(&self) -> String { fn id(&self) -> String {
self.id.clone() self.id.clone()
@ -1583,6 +1628,9 @@ fn execute_hook(logger: &Logger, h: &Hook, st: &OCIState) -> Result<()> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::skip_if_not_root;
use std::fs;
use std::os::unix::fs::MetadataExt;
#[test] #[test]
fn test_status_transtition() { fn test_status_transtition() {
@ -1601,4 +1649,27 @@ mod tests {
assert_eq!(pre_status, status.pre_status()); assert_eq!(pre_status, status.pre_status());
} }
} }
#[test]
fn test_set_stdio_permissions() {
skip_if_not_root!();
let meta = fs::metadata("/dev/stdin").unwrap();
let old_uid = meta.uid();
let uid = 1000;
set_stdio_permissions(uid).unwrap();
let meta = fs::metadata("/dev/stdin").unwrap();
assert_eq!(meta.uid(), uid);
let meta = fs::metadata("/dev/stdout").unwrap();
assert_eq!(meta.uid(), uid);
let meta = fs::metadata("/dev/stderr").unwrap();
assert_eq!(meta.uid(), uid);
// restore the uid
set_stdio_permissions(old_uid).unwrap();
}
} }