genpac 0.1.0

Sandbox for Gentoo ebuild development using bubblewrap
// Copyright (C) 2023 Gokul Das B
// SPDX-License-Identifier: GPL-3.0-or-later
//! Bubblewrap info processing
//!
//! This module accepts and polls the pipe FD through which bubblewrap returns data after
//! launching. The data is then decoded from JSON format. This data is useful in setting up the user
//! namespace for the child application. The data is later used for mapping UID/GID between root
//! namespace and sandbox's user namespace.

use crate::global::IDMapItem;
use crate::LogCommand;
use anyhow::Result as AResult;
use os_pipe::PipeReader;
use polling::{Event, Events, Poller};
use serde::Deserialize;
use std::io::Read;
use std::os::fd::AsRawFd;
use std::process::Command;

#[allow(dead_code)]
#[derive(Deserialize, Debug)]
pub(super) struct BWInfo {
    #[serde(rename = "child-pid")]
    pub child_pid: u64,
    #[serde(rename = "cgroup-namespace")]
    pub cgroup_namespace: Option<u64>,
    #[serde(rename = "ipc-namespace")]
    pub ipc_namespace: Option<u64>,
    #[serde(rename = "mnt-namespace")]
    pub mnt_namespace: Option<u64>,
    #[serde(rename = "pid-namespace")]
    pub pid_namespace: Option<u64>,
    #[serde(rename = "uts-namespace")]
    pub uts_namespace: Option<u64>,
}

impl BWInfo {
    // Wait for piped data from bwrap and decode it
    pub(super) fn acquire(pipe: &mut PipeReader) -> AResult<Self> {
        // IMPORTANT: PipeReader reads data only up to length of buffer. Capacity and dynamic
        // allocation of the buffer are simply neglected. The buffer therefore needs to be
        // pre-filled.
        let mut part = [0u8; 512];
        let mut full: Vec<u8> = Vec::new();
        let fd = pipe.as_raw_fd();

        // Prepare to poll the file
        let poller = Poller::new()?;
        let mut events = Events::new();
        unsafe {
            poller.add(fd, Event::readable(1))?;
        }

        // Poll until the proper end of stream is detected
        loop {
            // Wait for data to be ready on the pipe
            events.clear();
            poller.wait(&mut events, None)?;
            // Read data from the pipe
            let size = pipe.read(&mut part)?;

            // The trailing characters are a major headache. We remove them here.
            let mut seg = part[..size].to_vec();
            // Append the read data to the full vec
            full.append(&mut seg);

            // Decide whether to wait for more or to proceed (EOF?)
            if size >= 2 && &part[(size - 2)..size] == b"}\n" {
                break;
            } else {
                poller.modify(&pipe, Event::readable(1))?;
            }
        }

        // Convert to JSON and then decode
        Ok(serde_json::from_str(std::str::from_utf8(&full)?)?)
    }

    /// Do <U|G>ID Mapping of user namespace using newuidmap(1) & newgidmap(1)
    pub(super) fn map_ids(&self, uidmaps: &[IDMapItem], gidmaps: &[IDMapItem]) -> AResult<()> {
        let pid = self.child_pid;

        let mut newuidmap = Command::new("newuidmap");
        newuidmap.arg(pid.to_string());
        for (upper, lower, count) in uidmaps {
            newuidmap.arg(upper.to_string());
            newuidmap.arg(lower.to_string());
            newuidmap.arg(count.to_string());
        }
        newuidmap.log("UID Mapping");
        if !newuidmap.status()?.success() {
            const NUMERR: &str = "Failed to set UID map";
            log::error!("{NUMERR}");
            anyhow::bail!(NUMERR);
        }

        let mut newgidmap = Command::new("newgidmap");
        newgidmap.arg(pid.to_string());
        for (upper, lower, count) in gidmaps {
            newgidmap.arg(upper.to_string());
            newgidmap.arg(lower.to_string());
            newgidmap.arg(count.to_string());
        }
        newgidmap.log("GID Mapping");
        if !newgidmap.status()?.success() {
            const NGMERR: &str = "Failed to set GID map";
            log::error!("{NGMERR}");
            anyhow::bail!(NGMERR);
        }

        Ok(())
    }
}