agent: fixes the permissions of PID 1's STDIO

Fix 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.

Fixes: #1022

Signed-off-by: fupan.lfp <fupan.lfp@antfin.com>
This commit is contained in:
fupan.lfp 2020-10-22 20:37:14 +08:00
parent 710a4f3b76
commit e90aa7b417

View File

@ -39,6 +39,8 @@ use nix::sched::{self, CloneFlags};
use nix::sys::signal::{self, Signal};
use nix::sys::stat::{self, Mode};
use nix::unistd::{self, ForkResult, Gid, Pid, Uid};
use std::os::unix::fs::MetadataExt;
use std::os::unix::io::AsRawFd;
use protobuf::SingularPtrField;
@ -545,6 +547,12 @@ fn do_init_child(cwfd: RawFd) -> Result<()> {
let uid = Uid::from_raw(guser.uid);
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)?;
if !guser.additional_gids.is_empty() {
@ -639,6 +647,43 @@ fn do_init_child(cwfd: RawFd) -> Result<()> {
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 {
fn id(&self) -> String {
self.id.clone()
@ -1583,6 +1628,9 @@ fn execute_hook(logger: &Logger, h: &Hook, st: &OCIState) -> Result<()> {
#[cfg(test)]
mod tests {
use super::*;
use crate::skip_if_not_root;
use std::fs;
use std::os::unix::fs::MetadataExt;
#[test]
fn test_status_transtition() {
@ -1601,4 +1649,27 @@ mod tests {
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();
}
}