From 3f5fc7d16f357ec82eec1575c384cfd7a199fd3f Mon Sep 17 00:00:00 2001 From: Sangbum Kim Date: Wed, 6 Mar 2024 05:03:44 +0900 Subject: [PATCH] added initial commit --- .dockerignore | 5 + .gitignore | 208 ++++++++++++++++++++++++++++ .vscode/settings.json | 4 + Cargo.toml | 28 ++++ Dockerfile | 37 +++++ Dockerfile.debian | 32 +++++ cargo/config.toml | 2 + src/link.rs | 17 +++ src/link_gcc.rs | 26 ++++ src/main.rs | 314 ++++++++++++++++++++++++++++++++++++++++++ src/mem.rs | 18 +++ 11 files changed, 691 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 Cargo.toml create mode 100644 Dockerfile create mode 100644 Dockerfile.debian create mode 100644 cargo/config.toml create mode 100644 src/link.rs create mode 100644 src/link_gcc.rs create mode 100644 src/main.rs create mode 100644 src/mem.rs diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d7bf2f5 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +**/.git +/.devcontainer +/.vscode +/debug +/target \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f11d98c --- /dev/null +++ b/.gitignore @@ -0,0 +1,208 @@ +# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,intellij+all,windows,linux,macos,rust,rust-analyzer +# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,intellij+all,windows,linux,macos,rust,rust-analyzer + +### Intellij+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij+all Patch ### +# Ignore everything but code style settings and run configurations +# that are supposed to be shared within teams. + +.idea/* + +!.idea/codeStyles +!.idea/runConfigurations + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +### rust-analyzer ### +# Can be generated by other build systems other than cargo (ex: bazelbuild/rust_rules) +rust-project.json + + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,intellij+all,windows,linux,macos,rust,rust-analyzer + +/target diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..48e33ea --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ + +{ + "rust-analyzer.cargo.target": "aarch64-unknown-linux-musl", +} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..c177c22 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "init-wrapper" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +libc = { version = "0.2", default-features = false } +libc-print = "0.1.22" +errno = { version = "*", default-features = false } + +[profile.dev] +# This isn't required for development builds, but makes development +# build behavior match release builds. To enable unwinding panics +# during development, simply remove this line. +panic = "abort" + +[profile.release] +#opt-level = 'z' # Optimize for size. +lto = true # Enable Link Time Optimization +codegen-units = 1 # Reduce number of codegen units to increase optimizations. +panic = 'abort' # Abort on panic +strip = true # Strip symbols from binary* +debug-assertions = false +debug = false +rpath = false +incremental = false diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e8a2c06 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,37 @@ +#syntax=docker/dockerfile:1 + +## +## Build +## +FROM rust:1-alpine3.19 AS build +LABEL org.opencontainers.image.authors="Sangbum Kim " + +# set the workdir and copy the source into it +WORKDIR /app +COPY . /app + +ENV RUSTFLAGS='-C link-arg=-s -C link-arg=-fuse-ld=lld' + +RUN set -x && \ + apk add --no-cache \ + libcap-static \ + libcap-dev \ + lld \ + musl-dev + +RUN set -x && \ + cargo build --release && \ + ls -alh target/release/init-wrapper + # && \ + # ldd target/release/init-wrapper + +# RUN --mount=type=bind,rw,source=.,target=/host \ +# cp -avf target/release/init-wrapper /host/init-wrapper && \ +# ./target/release/init-wrapper + + +FROM scratch +COPY --from=build /app/target/release/init-wrapper /init-wrapper + + +# ENTRYPOINT ['/init-wrapper'] \ No newline at end of file diff --git a/Dockerfile.debian b/Dockerfile.debian new file mode 100644 index 0000000..3957e35 --- /dev/null +++ b/Dockerfile.debian @@ -0,0 +1,32 @@ +#syntax=docker/dockerfile:1 + +## +## Build +## +FROM rust:1-slim AS build +LABEL org.opencontainers.image.authors="Sangbum Kim " + +# set the workdir and copy the source into it +WORKDIR /app +COPY . /app + +# ENV RUSTFLAGS='-C link-arg=-s -C linker=rust-lld -C link-arg=-fuse-ld=lld' +# ENV RUSTFLAGS='-C link-arg=-s -C link-arg=-fuse-ld=lld' +# ENV RUSTFLAGS='-C target-feature=+crt-static -C link-arg=-s -C link-args=-nostartfiles -C link-arg=-nostdlib' +# ENV RUSTFLAGS='-C target-feature=+crt-static -C link-arg=-s' +# ENV RUSTFLAGS='-C target-feature=+crt-static -C link-arg=-s' +# ENV RUSTFLAGS='-C target-feature=+crt-static -C link-arg=-static -C link-arg=-s' +# ENV RUSTFLAGS='-C target-feature=+crt-static -C link-arg=-static -C link-arg=-s -C link-arg=-fuse-ld=lld' +# ENV RUSTFLAGS='-C target-feature=+crt-static -C link-arg=-s -C link-arg=-fuse-ld=lld' + +# do a release build +RUN set -x && \ + cargo build --release && \ + ldd target/release/init-wrapper + +#RUN --mount=type=bind,rw,source=.,target=/host \ +# cp -avf target/release/init-wrapper /host/init-wrapper + + +FROM scratch +COPY --from=build /app/target/release/init-wrapper /init-wrapper diff --git a/cargo/config.toml b/cargo/config.toml new file mode 100644 index 0000000..9016ce9 --- /dev/null +++ b/cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "aarch64-unknown-linux-musl" \ No newline at end of file diff --git a/src/link.rs b/src/link.rs new file mode 100644 index 0000000..864bcd5 --- /dev/null +++ b/src/link.rs @@ -0,0 +1,17 @@ +#![allow(non_camel_case_types, dead_code)] + +use libc::FILE; + +#[link(name = "c")] +extern "C" { + static stdin: *mut FILE; + static stdout: *mut FILE; + static stderr: *mut FILE; +} + +#[panic_handler] +#[inline(never)] +#[cfg(not(test))] +unsafe fn panic_handler(_: &core::panic::PanicInfo) -> ! { + libc::exit(2) +} diff --git a/src/link_gcc.rs b/src/link_gcc.rs new file mode 100644 index 0000000..ab7fa5b --- /dev/null +++ b/src/link_gcc.rs @@ -0,0 +1,26 @@ +#![allow(non_camel_case_types, dead_code)] + +use libc::{c_int, exit, FILE}; + +#[repr(C, align(16))] +struct f128 { + a: [u8; 16], +} + +#[no_mangle] +extern "C" fn __letf2(_a: f128, _b: f128) -> c_int { + 0 +} + +#[no_mangle] +extern "C" fn __unordtf2(_a: f128, _b: f128) -> c_int { + 0 +} + +#[no_mangle] +unsafe extern "C" fn _Unwind_Resume() -> ! { + exit(2) +} + +#[no_mangle] +extern "C" fn __gcc_personality_v0() {} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..442c126 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,314 @@ +#![no_std] +#![no_main] + +extern crate alloc; +mod link; +mod mem; + +use alloc::ffi::CString; +use core::ffi::CStr; +use core::ops::Sub; +use core::ptr::null; +use core::time::Duration; +use errno::{errno, Errno}; +use libc::{ + c_int, c_void, chdir, clock_gettime, execvp, mkdir, mode_t, mount, rmdir, syscall, timespec, + umount2, SYS_pivot_root, CLOCK_BOOTTIME, EXIT_FAILURE, EXIT_SUCCESS, MNT_DETACH, MS_BIND, + MS_LAZYTIME, MS_MOVE, MS_NODEV, MS_NOSUID, MS_PRIVATE, MS_RDONLY, MS_REC, MS_RELATIME, +}; +use libc_print::std_name::eprintln; + +struct Timespec { + ts: timespec, +} + +type SystemResult = Result<(), Errno>; + +impl Sub for Timespec { + type Output = Duration; + + fn sub(self, other: Timespec) -> Duration { + let sec = self.ts.tv_sec - other.ts.tv_sec; + let nsec = self.ts.tv_nsec - other.ts.tv_nsec; + if nsec < 0 { + Duration::from_secs(sec as u64) - Duration::from_nanos(nsec.unsigned_abs()) + } else { + Duration::from_secs(sec as u64) + Duration::from_nanos(nsec.unsigned_abs()) + } + } +} + +impl From for Timespec { + fn from(item: timespec) -> Self { + Timespec { ts: item } + } +} + +impl Into for Timespec { + fn into(self) -> timespec { + self.ts + } +} + +fn do_mount( + source: Option<&str>, + target: Option<&str>, + fs: Option<&str>, + flags: u64, + opt: Option<&str>, +) -> SystemResult { + // has ownership + let raw_src = source.map(|v| CString::new(v).ok()).flatten(); + let raw_tgt = target.map(|v| CString::new(v).ok()).flatten(); + let raw_fs = fs.map(|v| CString::new(v).ok()).flatten(); + let raw_fs_opt = opt.map(|v| CString::new(v).ok()).flatten(); + + unsafe { + if mount( + raw_src.as_ref().map(|v| v.as_ptr()).unwrap_or_else(null), + raw_tgt.as_ref().map(|v| v.as_ptr()).unwrap_or_else(null), + raw_fs.as_ref().map(|v| v.as_ptr()).unwrap_or_else(null), + flags, + raw_fs_opt.as_ref().map(|v| v.as_ptr()).unwrap_or_else(null) as *const c_void, + ) == -1 + { + Err(errno()) + } else { + Ok(()) + } + } +} +fn do_umount(path: &str, flags: i32) -> SystemResult { + // has ownership + let raw_path = CString::new(path).unwrap(); + unsafe { + if umount2(raw_path.as_ref().as_ptr(), flags) == -1 { + Err(errno()) + } else { + Ok(()) + } + } +} + +fn do_mkdir(path: &str, mode: mode_t) -> SystemResult { + // has ownership + let raw_path = CString::new(path).unwrap(); + unsafe { + if mkdir(raw_path.as_ref().as_ptr(), mode) == -1 { + Err(errno()) + } else { + Ok(()) + } + } +} +fn do_rmdir(path: &str) -> SystemResult { + // has ownership + let raw_path = CString::new(path).unwrap(); + unsafe { + if rmdir(raw_path.as_ref().as_ptr()) == -1 { + Err(errno()) + } else { + Ok(()) + } + } +} +fn do_chdir(path: &str) -> SystemResult { + // has ownership + let raw_path = CString::new(path).unwrap(); + unsafe { + if chdir(raw_path.as_ref().as_ptr()) == -1 { + Err(errno()) + } else { + Ok(()) + } + } +} + +fn pivot_root(new_root: &str, put_old: &str) -> SystemResult { + let raw_new_root = CString::new(new_root).unwrap(); + let raw_put_old = CString::new(put_old).unwrap(); + unsafe { + if syscall( + SYS_pivot_root, + raw_new_root.as_ref().as_ptr(), + raw_put_old.as_ref().as_ptr(), + ) == -1 + { + Err(errno()) + } else { + Ok(()) + } + } +} + +fn do_gettime() -> Result { + let mut time = timespec { + tv_sec: 0, + tv_nsec: 0, + }; + + unsafe { + if clock_gettime(CLOCK_BOOTTIME, &mut time) == -1 { + Err(errno()) + } else { + Ok(time.into()) + } + } +} + +fn replace_root() -> Result<(), ()> { + match do_mount(None, Some("/"), None, MS_REC | MS_PRIVATE, None) { + Err(e) => { + eprintln!("remount \"/\" with private option failed: {}", e); + return Err(()); + } + Ok(_) => eprintln!("\"/\" remounted"), + }; + + match do_mount( + Some("parentfs"), + Some("/run"), + Some("tmpfs"), + MS_LAZYTIME | MS_NODEV | MS_NOSUID | MS_RELATIME, + None, + ) { + Err(e) => { + eprintln!("mount \"/run\" failed: {}", e); + return Err(()); + } + Ok(_) => eprintln!("\"/run\" mounted"), + }; + + let ovr_dir_structures = [ + "/run/overlay", + "/run/overlay/lower", + "/run/overlay/upper", + "/run/overlay/work", + "/run/overlay/merged", + ]; + for path in ovr_dir_structures { + match do_mkdir(path, 0o0700) { + Err(e) => { + eprintln!("mkdir \"{}\" failed: {}", path, e); + return Err(()); + } + Ok(_) => eprintln!("\"{}\" created", path), + } + } + + match do_mount( + Some("/"), + Some("/run/overlay/lower"), + None, + MS_BIND | MS_RDONLY, + None, + ) { + Err(e) => { + eprintln!("mount \"/run/overlay/lower\" failed: {}", e); + return Err(()); + } + Ok(_) => eprintln!("\"/run/overlay/lower\" mounted"), + }; + + match do_mount( + Some("rootfs"), + Some("/run/overlay/merged"), + Some("overlay"), MS_RELATIME|MS_LAZYTIME, + Some("lowerdir=/run/overlay/lower,upperdir=/run/overlay/upper,workdir=/run/overlay/work,redirect_dir=on,uuid=on,metacopy=on,volatile"), + ) { + Err(e) => {eprintln!("mount \"/run/overlay/merged\" failed: {}", e); + return Err(()); + }, + Ok(_) => eprintln!("\"/run/overlay/merged\" mounted"), + }; + + match do_mkdir("/run/overlay/merged/oldroot", 0o0700) { + Err(e) => { + eprintln!("mkdir \"/run/overlay/merged/oldroot\" failed: {}", e); + return Err(()); + } + Ok(_) => eprintln!("\"/run/overlay/merged/oldroot\" created"), + } + + match pivot_root("/run/overlay/merged", "/run/overlay/merged/oldroot") { + Err(e) => { + eprintln!("pivot_root failed: {}", e); + return Err(()); + } + Ok(_) => eprintln!("pivot_root done"), + } + + match do_chdir("/") { + Err(e) => { + eprintln!("chdir \"/\" failed: {}", e); + return Err(()); + } + Ok(_) => eprintln!("chdir \"/\" done"), + } + + match do_mount( + Some("/oldroot/run"), + Some("/run"), + None, + MS_REC | MS_MOVE, + None, + ) { + Err(e) => { + eprintln!("moving mountpoint \"/oldroot/run\" failed: {}", e); + return Err(()); + } + Ok(_) => eprintln!("mountpoint \"/oldroot/run\" moved"), + }; + + match do_umount("/oldroot", MNT_DETACH) { + Err(e) => { + eprintln!("umount mountpoint \"/oldroot\" failed: {}", e); + return Err(()); + } + Ok(_) => eprintln!("mountpoint \"/oldroot/run\" unmounted"), + }; + + let unused_dirs = ["/oldroot", "/run/overlay/merged"]; + for path in unused_dirs { + match do_rmdir(path) { + Err(e) => { + eprintln!("remove \"{}\" failed: {}", path, e); + return Err(()); + } + Ok(_) => eprintln!("\"{}\" removed", path), + } + } + + Ok(()) +} + +#[no_mangle] +unsafe extern "C" fn main(_argc: c_int, argv: *const *const u8) -> c_int { + let start = match do_gettime() { + Err(err) => { + eprintln!("{}", err); + return EXIT_FAILURE; + } + Ok(v) => v, + }; + + match replace_root() { + Err(_) => return EXIT_FAILURE, + Ok(_) => eprintln!("rootfs replaced with overlayfs!"), + }; + + let end = match do_gettime() { + Err(err) => { + eprintln!("{}", err); + return EXIT_FAILURE; + } + Ok(v) => v, + }; + let elapsed = end - start; + eprintln!("processed in {:?}", elapsed); + + let init_path = CStr::from_bytes_with_nul_unchecked(b"/sbin/init\0"); + execvp(init_path.as_ptr(), argv); + + EXIT_SUCCESS +} diff --git a/src/mem.rs b/src/mem.rs new file mode 100644 index 0000000..661c343 --- /dev/null +++ b/src/mem.rs @@ -0,0 +1,18 @@ +use core::alloc::{GlobalAlloc, Layout}; +use libc::{c_void, free, malloc}; +/// The global allocator type. +#[derive(Default)] +pub struct Allocator; + +unsafe impl GlobalAlloc for Allocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + malloc(layout.size()) as *mut u8 + } + unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { + free(ptr as *mut c_void); + } +} + +/// The static global allocator. +#[global_allocator] +static GLOBAL_ALLOCATOR: Allocator = Allocator;