Слияние кода завершено, страница обновится автоматически
// Copyright (c) 2020 Huawei Technologies Co.,Ltd. All rights reserved.
//
// StratoVirt is licensed under Mulan PSL v2.
// You can use this software according to the terms and conditions of the Mulan
// PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
// http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
use std::process::Command;
use std::{
fs::{canonicalize, read_dir},
os::unix::prelude::CommandExt,
path::{Path, PathBuf},
process::Stdio,
};
use anyhow::{anyhow, bail, Context, Result};
use nix::fcntl::{fcntl, FcntlArg};
use crate::cgroup::{self, init_cgroup, parse_cgroup, CgroupCfg};
use crate::OzoneError;
use crate::{capability, namespace, syscall};
use util::arg_parser::ArgMatches;
const BASE_OZONE_PATH: &str = "/srv/ozone";
const SELF_FD: &str = "/proc/self/fd";
const MAX_STRING_LENGTH: usize = 255;
const MAX_ID_NUMBER: u32 = 65535;
const NEWROOT_FOLDERS: [&str; 3] = ["/", "/dev", "/dev/net"];
const NEWROOT_DEVICE_NR: usize = 6;
const NEWROOT_DEVICES: [&str; NEWROOT_DEVICE_NR] = [
"/dev/kvm",
"/dev/net/tun",
"/dev/vhost-net",
"/dev/vhost-vsock",
"/dev/urandom",
"/dev/null",
];
const NEWROOT_DEVICES_PERMISSION: [[u32; 3]; NEWROOT_DEVICE_NR] = [
[10, 232, 0o660],
[10, 200, 0o666],
[10, 238, 0o600],
[10, 241, 0o600],
[1, 9, 0o666],
[1, 3, 0o666],
];
/// OzoneHandler is used to handle data.
#[derive(Default)]
pub struct OzoneHandler {
name: String,
uid: u32,
gid: u32,
node: Option<String>,
cgroup: Option<CgroupCfg>,
netns_path: Option<String>,
capability: Option<String>,
exec_file_path: PathBuf,
chroot_dir: PathBuf,
source_file_paths: Vec<PathBuf>,
extra_args: Vec<String>,
}
impl OzoneHandler {
/// Create "OzoneHandler" from cmdline arguments.
///
/// # Arguments
///
/// * `args` - args parser.
pub fn new(args: &ArgMatches) -> Result<Self> {
let mut handler = OzoneHandler::default();
if let Some(name) = args.value_of("name") {
if name.len() > MAX_STRING_LENGTH {
bail!("Input name's length must be no more than 255");
}
handler.name = name;
}
if let Some(uid) = args.value_of("uid") {
let user_id = (uid)
.parse::<u32>()
.with_context(|| OzoneError::DigitalParseError("uid", uid))?;
if user_id > MAX_ID_NUMBER {
bail!("Input uid should be no more than 65535");
}
handler.uid = user_id;
}
if let Some(gid) = args.value_of("gid") {
let group_id = (gid)
.parse::<u32>()
.with_context(|| OzoneError::DigitalParseError("gid", gid))?;
if group_id > MAX_ID_NUMBER {
bail!("Input gid should be no more than 65535");
}
handler.gid = group_id;
}
if let Some(exec_file) = args.value_of("exec_file") {
handler.exec_file_path = canonicalize(exec_file)
.with_context(|| "Failed to parse exec file path to PathBuf")?;
}
if let Some(source_paths) = args.values_of("source_files") {
for path in source_paths.iter() {
handler
.source_file_paths
.push(canonicalize(path).with_context(|| {
format!("Failed to parse source path {:?} to PathBuf", &path)
})?);
}
}
if let Some(node) = args.value_of("numa") {
handler.node = Some(
(node)
.parse::<String>()
.with_context(|| OzoneError::DigitalParseError("numa", node))?,
);
}
if let Some(config) = args.values_of("cgroup") {
let mut cgroup_cfg = init_cgroup();
for cfg in config {
parse_cgroup(&mut cgroup_cfg, &cfg).with_context(|| "Failed to parse cgroup")?
}
handler.cgroup = Some(cgroup_cfg);
}
handler.extra_args = args.extra_args();
handler.netns_path = args.value_of("network namespace");
handler.capability = args.value_of("capability");
handler.chroot_dir = PathBuf::from(BASE_OZONE_PATH);
handler.chroot_dir.push(handler.exec_file_name()?);
handler.chroot_dir.push(Path::new(&handler.name));
Ok(handler)
}
/// Create directory for chroot.
fn create_chroot_dir(&self) -> Result<()> {
if self.chroot_dir.as_path().exists() {
bail!(
"Process name for {} in path {:?} has already exists",
self.exec_file_name()?,
&self.chroot_dir.as_path()
);
}
std::fs::create_dir_all(&self.chroot_dir)
.with_context(|| format!("Failed to create folder {:?}", &self.chroot_dir))?;
Ok(())
}
/// Copy input executable binary file to chroot directory.
fn copy_exec_file(&self) -> Result<()> {
let exec_file_name = self.exec_file_name()?;
let mut chroot_dir = self.chroot_dir.clone();
chroot_dir.push(&exec_file_name);
std::fs::copy(&self.exec_file_path, chroot_dir)
.with_context(|| format!("Failed to copy {:?} to new chroot dir", exec_file_name))?;
Ok(())
}
/// Bind mount 'file_path' into chroot directory.
///
/// # Arguments
///
/// * `file_path` - args parser.
fn bind_mount_file(&self, file_path: &Path) -> Result<()> {
let file_name = file_path.file_name().with_context(|| "Empty file path")?;
let mut new_root_dir = self.chroot_dir.clone();
new_root_dir.push(file_name);
if file_path.is_dir() {
std::fs::create_dir_all(&new_root_dir)
.with_context(|| format!("Failed to create directory: {:?}", &new_root_dir))?;
} else {
std::fs::File::create(&new_root_dir)
.with_context(|| format!("Failed to create file: {:?}", &new_root_dir))?;
}
// new_root_dir.to_str().unwrap() is safe, because new_root_dir is not empty.
syscall::mount(
file_path.to_str(),
new_root_dir.to_str().unwrap(),
libc::MS_BIND | libc::MS_SLAVE,
)
.with_context(|| format!("Failed to mount file: {:?}", &file_path))?;
let data = std::fs::metadata(&new_root_dir)?;
if !file_path.is_dir() && data.len() == 0 {
bail!("File: {:?} is empty", &new_root_dir);
}
syscall::chown(new_root_dir.to_str().unwrap(), self.uid, self.gid)
.with_context(|| format!("Failed to change owner for source: {:?}", &file_path))?;
Ok(())
}
/// Get exec file name.
fn exec_file_name(&self) -> Result<String> {
if let Some(file_name) = self.exec_file_path.file_name() {
return Ok(file_name.to_string_lossy().into());
} else {
bail!("Failed to exec file name")
}
}
fn create_newroot_folder(&self, folder: &str) -> Result<()> {
std::fs::create_dir_all(folder)
.with_context(|| format!("Failed to create folder: {:?}", &folder))?;
syscall::chmod(folder, 0o700)
.with_context(|| format!("Failed to chmod to 0o700 for folder: {:?}", &folder))?;
syscall::chown(folder, self.uid, self.gid)
.with_context(|| format!("Failed to change owner for folder: {:?}", &folder))?;
Ok(())
}
fn create_newroot_device(
&self,
dev_path: &str,
dev_major: u32,
dev_minor: u32,
mode: u32,
) -> Result<()> {
let dev = syscall::makedev(dev_major, dev_minor)?;
syscall::mknod(dev_path, libc::S_IFCHR | libc::S_IWUSR | libc::S_IRUSR, dev)
.with_context(|| format!("Failed to call mknod for device: {:?}", &dev_path))?;
syscall::chmod(dev_path, mode)
.with_context(|| format!("Failed to change mode for device: {:?}", &dev_path))?;
syscall::chown(dev_path, self.uid, self.gid)
.with_context(|| format!("Failed to change owner for device: {:?}", &dev_path))?;
Ok(())
}
/// Realize OzoneHandler.
pub fn realize(&self) -> Result<()> {
// First, disinfect the process.
disinfect_process().with_context(|| "Failed to disinfect process")?;
self.create_chroot_dir()?;
self.copy_exec_file()?;
for source_file_path in self.source_file_paths.iter() {
self.bind_mount_file(source_file_path)?;
}
let exec_file = self.exec_file_name()?;
if let Some(node) = self.node.clone() {
cgroup::set_numa_node(&node, &exec_file, &self.name)
.with_context(|| "Failed to set numa node")?;
}
if let Some(cgroup) = &self.cgroup {
cgroup::realize_cgroup(cgroup, exec_file, self.name.clone())
.with_context(|| "Failed to realize cgroup")?;
}
namespace::set_uts_namespace("Ozone")?;
namespace::set_ipc_namespace()?;
if let Some(netns_path) = &self.netns_path {
namespace::set_network_namespace(netns_path)?;
}
namespace::set_mount_namespace(self.chroot_dir.to_str().unwrap())?;
for folder in NEWROOT_FOLDERS.iter() {
self.create_newroot_folder(folder)?;
}
for index in 0..NEWROOT_DEVICE_NR {
self.create_newroot_device(
NEWROOT_DEVICES[index],
NEWROOT_DEVICES_PERMISSION[index][0],
NEWROOT_DEVICES_PERMISSION[index][1],
NEWROOT_DEVICES_PERMISSION[index][2],
)?;
}
if let Some(capability) = &self.capability {
capability::set_capability_for_ozone(capability)
.with_context(|| "Failed to set capability for ozone.")?;
} else {
capability::clear_all_capabilities()
.with_context(|| "Failed to clean all capability for ozone.")?;
}
let mut chroot_exec_file = PathBuf::from("/");
chroot_exec_file.push(self.exec_file_name()?);
Err(anyhow!(OzoneError::ExecError(
Command::new(chroot_exec_file)
.gid(self.gid)
.uid(self.uid)
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.args(&self.extra_args)
.exec(),
)))
}
/// Clean the environment.
pub fn teardown(&self) -> Result<()> {
// Unmount source file in chroot dir path.
for source_file_path in self.source_file_paths.clone().into_iter() {
let mut chroot_path = self.chroot_dir.clone();
let source_file_name = source_file_path.file_name();
let file_name = source_file_name.with_context(|| "Source file is empty")?;
chroot_path.push(file_name);
if chroot_path.exists() {
syscall::umount(chroot_path.to_str().unwrap())
.with_context(|| format!("Failed to umount resource: {:?}", file_name))?
}
}
std::fs::remove_dir_all(&self.chroot_dir)
.with_context(|| "Failed to remove chroot dir path")?;
if self.node.is_some() {
cgroup::clean_node(self.exec_file_name()?, self.name.clone())
.with_context(|| "Failed to clean numa node")?;
}
if let Some(cgroup) = &self.cgroup {
cgroup::clean_cgroup(cgroup, self.exec_file_name()?, self.name.clone())
.with_context(|| "Failed to remove cgroup directory")?;
}
Ok(())
}
}
/// Disinfect the process before launching the ozone process.
fn disinfect_process() -> Result<()> {
let fd_entries = read_dir(SELF_FD).with_context(|| "Failed to open process fd proc")?;
let mut open_fds = vec![];
for entry in fd_entries {
if entry.is_err() {
break;
}
let file_name = entry.unwrap().file_name();
let file_name = file_name.to_str().unwrap_or("0");
let fd = file_name.parse::<libc::c_int>().unwrap_or(0);
if fd > 2 {
open_fds.push(fd);
}
}
for fd in open_fds {
if fcntl(fd, FcntlArg::F_GETFD).is_ok() {
syscall::close(fd).with_context(|| format!("Failed to close fd: {}", fd))?
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use std::env;
pub use super::*;
fn create_handler() -> OzoneHandler {
let mut dir = env::temp_dir();
dir.push("test_ozone_example");
dir.push("stratovirt");
let exec_file_path = dir.clone();
dir.pop();
let chroot_dir = PathBuf::from("/srv/ozone/ozone");
let mut source_file_paths = Vec::new();
dir.push("rootfs");
source_file_paths.push(dir.clone());
dir.pop();
dir.push("vmlinux.bin");
source_file_paths.push(dir);
OzoneHandler {
name: "ozone".to_string(),
uid: 100,
gid: 100,
exec_file_path,
netns_path: None,
chroot_dir,
source_file_paths,
extra_args: Vec::new(),
capability: None,
node: None,
cgroup: None,
}
}
#[test]
fn test_disinfect_process() {
assert!(disinfect_process().is_ok());
}
#[test]
fn test_exec_file_name() {
let handler = create_handler();
let exec_file = handler.exec_file_name();
assert!(exec_file.is_ok());
let exec_file = exec_file.unwrap();
assert_eq!(exec_file, "stratovirt");
}
}
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )