diff --git a/demos/runtime_boot/README.md b/demos/runtime_boot/README.md new file mode 100644 index 00000000..f4722fd7 --- /dev/null +++ b/demos/runtime_boot/README.md @@ -0,0 +1,75 @@ +# Runtime boot pre-generated UnionFS image + +Generally, every Occlum instance has to pass the `Occlum build` process. +In some scenarios, mount and boot a pre-generated UnionFS image without `Occlum build` is a good feature. This demo introduces a way to runtime boot a BASH demo. + +## Flow + +### First, build a [`BASH`](../bash) Occlum instance + +The later step will use the image content to generate UnionFS image. + +### Build and start a [`gen_rootfs`](./gen_rootfs) Occlum instance + +This `gen_rootfs` mounts a empty UnionFS, copy the BASH Occlum image content to the mount point, unmount the UnionFS. It generates an encrypted UnionFS image containing the BASH image content. The **key** used in this demo is `"c7-32-b3-ed-44-df-ec-7b-25-2d-9a-32-38-8d-58-61"`. + +### Build customized [`init`](./init) + +Occlum [`default init`](../../tools/init) calls syscall (363) `MountRootFS` to mount and boot Occlum instance generated by normal `occlum build`. +``` +(MountRootFS = 363) => do_mount_rootfs(key_ptr: *const sgx_key_128bit_t, rootfs_config: *const user_rootfs_config) +``` +The first parameter `key_ptr` is optional. +The second parameter `rootfs_config` needs to be set as NULL. + +But for runtime booting pre-generated UnionFS image, The first parameter `key_ptr` is must to have, the second parameter `rootfs_config` needs have valid members. +``` +struct user_rootfs_config { + // UnionFS type rootfs upper layer, read-write layer + upper_layer_path: *const i8, + // UnionFS type rootfs lower layer, read-only layer + lower_layer_path: *const i8, + entry_point: *const i8, + // HostFS source path + hostfs_source: *const i8, + // HostFS target path, default value is "/host" + hostfs_target: *const i8, +} +``` + +In this demo, parameters values are provided as below. + +* **rootfs_key** +The key to encrypt/decrypt the rootfs, here it is `"c7-32-b3-ed-44-df-ec-7b-25-2d-9a-32-38-8d-58-61". + +* **rootfs_upper_layer** +The upper layer path of the unionfs type rootfs. In this case, it is relative path `"../gen_rootfs_instance/mnt_unionfs/upper"`. + +* **rootfs_lower_layer** +The lower layer path of the unionfs type rootfs. In this case, it is relative path `"../gen_rootfs_instance/mnt_unionfs/lower"`. + +* **rootfs_entry** +The entry point of the rootfs. In his case, it is `"/bin"`. + +* **hostfs_source** +It is set to be `/tmp` in this case. + +In this example customized init, the above parameters are declared in the source [`main.rs`](./init/src/main.rs). In real case, they could be acquired by LA/RA or by modifying the PAL api `pal_run_init_process`. + +### Build a boot template Occlum instance + +This template uses the customized init. The RootFS image is not important, which will be replaced during boot. + +All above steps could be done with one [`script`](./build_content.sh). +``` +./build_content.sh +``` + +After running the script, runtime boot BASH could be done as below even if the default RootFS image has no BASH function. +``` +# cd boot_instance +# occlum run /bin/occlum_bash_test.sh +``` + + + diff --git a/demos/runtime_boot/boot_template.yaml b/demos/runtime_boot/boot_template.yaml new file mode 100644 index 00000000..87a511a5 --- /dev/null +++ b/demos/runtime_boot/boot_template.yaml @@ -0,0 +1,7 @@ +includes: + - base.yaml +targets: + - target: /bin + copy: + - files: + - /usr/bin/hostname diff --git a/demos/runtime_boot/build_content.sh b/demos/runtime_boot/build_content.sh new file mode 100755 index 00000000..316cfad3 --- /dev/null +++ b/demos/runtime_boot/build_content.sh @@ -0,0 +1,90 @@ +#!/bin/bash +set -e + +script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +export BASH_DEMO_DIR="${script_dir}/../bash" +export INIT_DIR="${script_dir}/init" + +UNIONFS_DIR="${script_dir}/gen_rootfs_instance/mnt_unionfs" +ENCRIP_KEY="c7-32-b3-ed-44-df-ec-7b-25-2d-9a-32-38-8d-58-61" + +function build_bash_demo() +{ + pushd ${BASH_DEMO_DIR} + rm -rf occlum_instance && occlum new occlum_instance + + cd occlum_instance + rm -rf image + copy_bom -f ../bash.yaml --root image --include-dir /opt/occlum/etc/template + + new_json="$(jq '.resource_limits.user_space_size = "600MB" | + .resource_limits.kernel_space_stack_size ="2MB" ' Occlum.json)" && \ + echo "${new_json}" > Occlum.json + + occlum build + popd +} + +function build_init() +{ + pushd ${INIT_DIR} + occlum-cargo clean + occlum-cargo build --release + popd +} + +function build_and_gen_rootfs() +{ + pushd gen_rootfs + cargo build + popd + + # initialize occlum workspace + rm -rf gen_rootfs_instance && occlum new gen_rootfs_instance + pushd gen_rootfs_instance + + new_json="$(jq '.resource_limits.user_space_size = "1000MB" | + .resource_limits.kernel_space_heap_size= "512MB" | + .resource_limits.kernel_space_stack_size= "16MB" ' Occlum.json)" && \ + echo "${new_json}" > Occlum.json + + rm -rf image + copy_bom -f ../gen_rootfs.yaml --root image --include-dir /opt/occlum/etc/template + + occlum build + + mkdir -p mnt_unionfs/lower + mkdir -p mnt_unionfs/upper + mkdir rootfs + cp -rf ${BASH_DEMO_DIR}/occlum_instance/image/* rootfs/ + + occlum run /bin/gen_rootfs ${ENCRIP_KEY} + + popd +} + +function build_boot_template() +{ + rm -rf boot_instance && occlum new boot_instance + pushd boot_instance + + new_json="$(jq '.resource_limits.user_space_size = "600MB" | + .resource_limits.kernel_space_stack_size ="2MB" ' Occlum.json)" && \ + echo "${new_json}" > Occlum.json + + rm -rf image + copy_bom -f ../boot_template.yaml --root image --include-dir /opt/occlum/etc/template + + # Update init + rm -rf initfs + copy_bom -f ../init.yaml --root initfs --include-dir /opt/occlum/etc/template + + occlum build + popd +} + +build_bash_demo +build_and_gen_rootfs +build_init +build_boot_template diff --git a/demos/runtime_boot/gen_rootfs.yaml b/demos/runtime_boot/gen_rootfs.yaml new file mode 100644 index 00000000..286c7228 --- /dev/null +++ b/demos/runtime_boot/gen_rootfs.yaml @@ -0,0 +1,7 @@ +includes: + - base.yaml +targets: + - target: /bin + copy: + - files: + - ../gen_rootfs/target/debug/gen_rootfs diff --git a/demos/runtime_boot/gen_rootfs/Cargo.toml b/demos/runtime_boot/gen_rootfs/Cargo.toml new file mode 100644 index 00000000..0bd8f85b --- /dev/null +++ b/demos/runtime_boot/gen_rootfs/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "gen_rootfs" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +libc = "0.2" +nix = "0.23.0" +fs_extra = "1.2" diff --git a/demos/runtime_boot/gen_rootfs/src/main.rs b/demos/runtime_boot/gen_rootfs/src/main.rs new file mode 100644 index 00000000..2fed4956 --- /dev/null +++ b/demos/runtime_boot/gen_rootfs/src/main.rs @@ -0,0 +1,74 @@ +use std::env; +use std::fs; +use std::path::Path; +use nix::mount::{mount, umount, MsFlags}; +use fs_extra::dir::{copy, CopyOptions}; + +fn main() { + let args: Vec = env::args().collect(); + println!("{:?}", args); + fs::create_dir("/mount").unwrap(); + + let fs_type = "unionfs"; + let mount_path = Path::new("/mount"); + let source = Path::new("unionfs"); + let flags = MsFlags::empty(); + let key = &args[1]; + let options = format!( + "lowerdir={},upperdir={},key={}", + "./mnt_unionfs/lower", + "./mnt_unionfs/upper", + key + ); + + println!("{:#?} {:#?} {:#?} {:#?} {:#?}", source, mount_path, fs_type, flags, options.as_str()); + mount( + Some(source), + mount_path, + Some(fs_type), + flags, + Some(options.as_str()), + ).unwrap(); + + println!("Copy rootfs content to /mount"); + let copy_options = CopyOptions::new(); + let paths = fs::read_dir("/host/rootfs").unwrap(); + + for entry in paths { + let path = entry.unwrap().path(); + println!("Name: {}", path.display()); + copy(path, "/mount", ©_options).unwrap(); + } + + println!("List directories in {:#?}", mount_path); + let paths = fs::read_dir("/mount").unwrap(); + + for path in paths { + println!("Name: {}", path.unwrap().path().display()) + } + + println!("Unmount {:#?}", mount_path); + umount(mount_path).unwrap(); + + println!("Do mount again"); + fs::create_dir("/mount2").unwrap(); + let mount_path = Path::new("/mount2"); + println!("{:#?} {:#?} {:#?} {:#?} {:#?}", source, mount_path, fs_type, flags, options.as_str()); + mount( + Some(source), + mount_path, + Some(fs_type), + flags, + Some(options.as_str()), + ).unwrap(); + + println!("List directories in {:#?}", mount_path); + let paths = fs::read_dir("/mount2").unwrap(); + + for path in paths { + println!("Name: {}", path.unwrap().path().display()) + } + + println!("Unmount {:#?}", mount_path); + umount(mount_path).unwrap(); +} diff --git a/demos/runtime_boot/init.yaml b/demos/runtime_boot/init.yaml new file mode 100644 index 00000000..c657a53b --- /dev/null +++ b/demos/runtime_boot/init.yaml @@ -0,0 +1,7 @@ +includes: + - base.yaml +targets: + - target: /bin/ + copy: + - files: + - ${INIT_DIR}/target/x86_64-unknown-linux-musl/release/init diff --git a/demos/runtime_boot/init/Cargo.toml b/demos/runtime_boot/init/Cargo.toml new file mode 100644 index 00000000..a1903d46 --- /dev/null +++ b/demos/runtime_boot/init/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "init" +version = "0.0.1" +authors = ["LI Qing geding.lq@antgroup.com"] +edition = "2018" + +[dependencies] +libc = "0.2.84" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" diff --git a/demos/runtime_boot/init/src/main.rs b/demos/runtime_boot/init/src/main.rs new file mode 100644 index 00000000..b68cc580 --- /dev/null +++ b/demos/runtime_boot/init/src/main.rs @@ -0,0 +1,117 @@ +extern crate libc; +extern crate serde; +extern crate serde_json; + +use libc::syscall; + +use std::io::prelude::*; +use std::error::Error; +use std::fs::File; +use std::io::{ErrorKind, Read}; + +use std::ffi::CString; +use std::env; + + + +fn main() -> Result<(), Box> { + let args: Vec = env::args().collect(); + println!("{:?}", args); + + // TODO: Get the rootfs key and other parameters through RA/LA or PAL + let rootfs_key = b"c7-32-b3-ed-44-df-ec-7b-25-2d-9a-32-38-8d-58-61"; + let rootfs_upper_layer = "../gen_rootfs_instance/mnt_unionfs/upper"; + let rootfs_lower_layer = "../gen_rootfs_instance/mnt_unionfs/lower"; + let rootfs_entry = "/bin"; + + // Get the key of FS image if needed + let key = { + // TODO: Get the key through RA or LA + let mut file = File::create("/etc/image_key")?; + // Writes key. + file.write(rootfs_key)?; + + const IMAGE_KEY_FILE: &str = "/etc/image_key"; + let key_str = load_key(IMAGE_KEY_FILE)?; + let mut key: sgx_key_128bit_t = Default::default(); + parse_str_to_bytes(&key_str, &mut key)?; + Some(key) + }; + + let key_ptr = key + .as_ref() + .map(|key| key as *const sgx_key_128bit_t) + .unwrap_or(std::ptr::null()); + + // Mount the image + const SYS_MOUNT_FS: i64 = 363; + + // Set rootfs parameters + let upper_layer_path = + CString::new(rootfs_upper_layer).expect("CString::new failed"); + let lower_layer_path = + CString::new(rootfs_lower_layer).expect("CString::new failed"); + let entry_point = CString::new(rootfs_entry).expect("CString::new failed"); + let hostfs_source = CString::new("/tmp").expect("CString::new failed"); + let rootfs_config: user_rootfs_config = user_rootfs_config { + upper_layer_path: upper_layer_path.as_ptr(), + lower_layer_path: lower_layer_path.as_ptr(), + entry_point: entry_point.as_ptr(), + hostfs_source: hostfs_source.as_ptr(), + hostfs_target: std::ptr::null() + }; + + let ret = unsafe { syscall( + SYS_MOUNT_FS, + key_ptr, + &rootfs_config) + }; + if ret < 0 { + return Err(Box::new(std::io::Error::last_os_error())); + } + + Ok(()) +} + +#[allow(non_camel_case_types)] +type sgx_key_128bit_t = [u8; 16]; + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +#[allow(non_camel_case_types)] +struct user_rootfs_config { + // UnionFS type rootfs upper layer, read-write layer + upper_layer_path: *const i8, + // UnionFS type rootfs lower layer, read-only layer + lower_layer_path: *const i8, + entry_point: *const i8, + // HostFS source path + hostfs_source: *const i8, + // HostFS target path, default value is "/host" + hostfs_target: *const i8, +} + +fn load_key(key_path: &str) -> Result> { + let mut key_file = File::open(key_path)?; + let mut key = String::new(); + key_file.read_to_string(&mut key)?; + Ok(key.trim_end_matches(|c| c == '\r' || c == '\n').to_string()) +} + +fn parse_str_to_bytes(arg_str: &str, bytes: &mut [u8]) -> Result<(), Box> { + let bytes_str_vec = { + let bytes_str_vec: Vec<&str> = arg_str.split('-').collect(); + if bytes_str_vec.len() != bytes.len() { + return Err(Box::new(std::io::Error::new( + ErrorKind::InvalidData, + "The length or format of Key/MAC string is invalid", + ))); + } + bytes_str_vec + }; + + for (byte_i, byte_str) in bytes_str_vec.iter().enumerate() { + bytes[byte_i] = u8::from_str_radix(byte_str, 16)?; + } + Ok(()) +}