From 8be59c5b0a1e4e88dff973cdbc5dfb6840098381 Mon Sep 17 00:00:00 2001 From: Parke Date: Mon, 28 Jun 2021 10:41:40 -0700 Subject: [PATCH] Various improvements. See changelog.txt. --- changelog.txt | 10 ++++-- lxroot.cpp | 85 ++++++++++++++++++++++++++++++++------------------- readme.md | 20 +++++++----- unit.sh | 40 ++++++++++++++++++++++-- 4 files changed, 111 insertions(+), 44 deletions(-) diff --git a/changelog.txt b/changelog.txt index 64ce81b..fbcd80d 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,11 +1,17 @@ +20210627 +Implemented environment passthru via -e or --env. +Added 'e' and 'w' indicators to command prompt. +Omit the newroot-option to remain in the external mount namespace. + + 20210624 Fix GitHub issue #3, as follows: If remount readonly fails, retry with different flags until success. -Split Lxroot :: env() into two parts. Second part happens after umount2(). -Disabled some poorly-formed / overly-restrictive unit tests. +Split Lxroot::env() into two parts. Second part happens after umount2(). +Disabled some poorly-formed (i.e. overly-restrictive) unit tests. 20210621 diff --git a/lxroot.cpp b/lxroot.cpp index efd3312..863c458 100644 --- a/lxroot.cpp +++ b/lxroot.cpp @@ -5,12 +5,12 @@ // Distributed under GPLv3 (see end of file) WITHOUT ANY WARRANTY. -#define LXROOT_VERSION "0.0.20210624" +#define LXROOT_VERSION "0.0.20210628" const char * help = // xxhe ------------------------------------- help "\n" -"usage: lxroot [mode] newroot [options] [--] [command [arg ...] ]\n\n" +"usage: lxroot [[mode] newroot] [options] [--] [command [arg ...] ]\n\n" "options\n" " -short one or more short options\n" @@ -35,11 +35,11 @@ const char * help2 = // xxhe ----------------------------------- help2 // short options ( for plannig purposes only, possibly inaccurate ) // . . . . . . -// a f hostfs(?) k p PID(?) u UID(?) z -// b g l q v verbose -// c h hostname? m MOUNT(?) r root w write -// d dbus(?) i n NET s /sys(?) x x11 -// e env(?) j o t y +// a - f - hostfs k - p - PID u - UID z +// b - g - l - q - v - verbose +// c - h - hstname m - MOUNT r root w write +// d - dbus i - n NET s - /sys x x11 +// e environ j - o - t y // // A F K P U Z // B G L Q V @@ -48,8 +48,8 @@ const char * help2 = // xxhe ----------------------------------- help2 // E J O T Y // // long options ( for plannig purposes only, possibly inaccurate ) -// dbus env help help-more network pid pulseaudio root trace uid -// verbose version write x11 +// dbus env help help-more network pid pulseaudio root trace uid +// verbose version write x11 const char * help_more = // xxlo ----------------------------- help_more @@ -62,6 +62,7 @@ const char * help_more = // xxlo ----------------------------- help_more "SHORT OPTIONS\n\n" +" e import (almost) all external environment variables\n" " n allow network access (CLONE_NEWNET = 0)\n" " r simulate root user (map uid and gid to zero)\n" " w allow full write access to all read-auto binds\n" @@ -69,7 +70,7 @@ const char * help_more = // xxlo ----------------------------- help_more "LONG OPTIONS\n\n" -// " --env import all environment variables\n" +" --env import (almost) all external environment variables\n" " --help display help\n" " --help-more display more help\n" " --network allow network access (CLONE_NEWNET = 0)\n" @@ -121,9 +122,13 @@ const char * help_more = // xxlo ----------------------------- help_more "Note that the newroot, full-overlay, and partial-overlay options all\n" "have the same form, namely: [mode] path\n\n" -"The first option of this form is the newroot option. The newroot\n" +"The first option of this form is the newroot-option. The newroot-\n" "option specfies the newroot.\n\n" +"If no newroot-option is specified, then lxroot will neither bind,\n" +"chroot, nor pivot. This is useful to simulate root or deny network\n" +"access while retaining the current mount namespace.\n\n" + "FULL OVERLAY\n\n" "Zero or more full-overlay options may occur anywhere before the first\n" @@ -1526,6 +1531,7 @@ struct Option_Reader // xxop -------------------- struct Option_Reader void do_command () { // ------------------ Option_Reader do_command + assert ( command == nullptr ); type = o_command; command |= p; }; @@ -1557,7 +1563,6 @@ struct Option_Reader // xxop -------------------- struct Option_Reader void path_or_command () { // -------- Option_Reader path_or_command // path_or_command - if ( command ) { p == command ? do_command() : path(); return; } bool found = Lib::is_dir( overlay ? overlay + "/" + arg0 : arg0 ); found ? path() : do_command(); } @@ -1708,6 +1713,7 @@ struct Init_Tool { // xxin ------------------------- struct Init_Tool case o_cd: cd(a); break; case o_command: command(a); break; + case o_env: mut .opt_env = o_env; break; case o_full: mut .guestname = a.arg0; break; case o_help: Lib :: help_print(); break; case o_help_more: Lib :: help_more_print(); break; @@ -1742,15 +1748,20 @@ struct Init_Tool { // xxin ------------------------- struct Init_Tool static void command ( const Option & a ) { // --- Init_Tool command + + /* 20210626 obsolete if ( st.newroot == nullptr && a.arg0 ) { printe ( "lxroot directory not found %s", a.arg0.s ); exit ( 1 ); } + */ + mut .command = a.command; } static void shortopt ( const Option & a ) { // - Init_Tool shortopt for ( mstr p = a.arg0.s+1 ; *p ; p++ ) { switch ( *p ) { + case 'e': mut .opt_env = o_env; break; case 'n': mut .opt_network = o_network; break; case 'r': mut .opt_root = o_root; break; case 'w': mut .opt_write = o_write; break; @@ -1829,8 +1840,10 @@ struct Env_Tool : Lib { // xxen --------------------- struct Env_Tool || st.opt_root == o_root || st.opt_x11 == o_x11 ) { opts = "-"; + if ( st.opt_env == o_env ) { opts += "e"; } if ( st.opt_network == o_network ) { opts += "n"; } if ( st.opt_root == o_root ) { opts += "r"; } + if ( st.opt_write == o_write ) { opts += "w"; } if ( st.opt_x11 == o_x11 ) { opts += "x"; } } ostr ps1 = "PS1="; @@ -1863,12 +1876,11 @@ struct Env_Tool : Lib { // xxen --------------------- struct Env_Tool || is_busybox ( st.command[0] ) ) { ps1_bash(); } } - /* 20210602 temporarily disabled - void passthru () { // --------------------------- Env_Tool passthru + static void passthru () { // -------------------- Env_Tool passthru if ( st.opt_env == o_env ) { - for ( Argv p (environ) ; * p ; p++ ) { - env .soft ( * p ); } } } - */ + for ( Argv p (environ) ; * p ; p ++ ) { + trace1 ( "env_tool passthru %s", p[0].s ); + mut .env .soft ( * p ); } } } public: @@ -1882,7 +1894,8 @@ struct Env_Tool : Lib { // xxen --------------------- struct Env_Tool static void after_pivot () { // -------------- Env_Tool after_pivot shell(); // argv(); // depends on env.SHELL - ps1(); } // depends on command[0] + ps1(); // depends on command[0] + passthru(); } }; // end class Env_Tool ------------------------ end class Env_Tool @@ -1898,7 +1911,9 @@ class Lxroot : Lib { // xxlx ------------------------- class Lxroot void init () { // ------------------------------------- Lxroot init q.options ( Init_Tool :: process ); - if ( st.newroot == nullptr ) { help_print ( 1 ); } } + // 20210626 obsolete + // if ( st.newroot == nullptr ) { help_print ( 1 ); } } + return; } void unshare () { // ------------------------------- Lxroot unshare @@ -1940,6 +1955,7 @@ class Lxroot : Lib { // xxlx ------------------------- class Lxroot void bind () { // ------------------------------------- Lxroot bind + if ( st.newroot == nullptr ) { return; } q.binds ( [](auto b) { sys .bind ( b.newroot_dst, b.src ); } ); // note /proc is mounted later on in Lxroot :: proc(). @@ -1959,7 +1975,8 @@ class Lxroot : Lib { // xxlx ------------------------- class Lxroot static void expose_path // ----------------------- Lxroot expose_path ( str path, opt readauto = o_none ) { - if ( path == nullptr ) { return; } + if ( st.newroot == nullptr ) { return; } + if ( path == nullptr ) { return; } mfrag parent; opt mode; q.calculate_parent ( path, parent, mode ); @@ -2009,7 +2026,11 @@ class Lxroot : Lib { // xxlx ------------------------- class Lxroot void pivot_prepare ( str pivot ) { // -------- Lxroot pivot_prepare - if ( pivot == nullptr ) { die1 ( "pivot_preapre pivot is nullptr" ); } + + // 20210626 obsolete + //if( pivot == nullptr ) { die1 ( "pivot_preapre pivot is nullptr" ); } + + // verify that pivot has at least one sub-direcotry (for put_old) assert ( put_old == nullptr ); q.scandir ( pivot, [&](auto e) { @@ -2020,6 +2041,7 @@ class Lxroot : Lib { // xxlx ------------------------- class Lxroot void pivot () { // ----------------------------------- Lxroot pivot + if ( st.newroot == nullptr ) { return; } pivot_prepare ( st.newroot ); sys .pivot ( st.newroot, st.newroot + put_old ); sys .chdir ( "/" ); @@ -2054,9 +2076,11 @@ class Lxroot : Lib { // xxlx ------------------------- class Lxroot // 20201213 fork()ing early complicates debugging with gdb. // Perhaps I should implement fork()ing both early and late? - if ( st.newroot ) { - sys .mount ( "proc", "/proc", "proc" ); - sys .umount2 ( put_old.s, MNT_DETACH ); } } + if ( st.newroot ) { sys .mount ( "proc", "/proc", "proc" ); } } + + + void umount2 () { // ------------------------------- Lxroot umount2 + if ( st.newroot ) { sys .umount2 ( put_old.s, MNT_DETACH ); } } void chdir () { // ----------------------------------- Lxroot chdir @@ -2070,11 +2094,6 @@ class Lxroot : Lib { // xxlx ------------------------- class Lxroot if ( st .newroot && is_dir ( home ) ) { sys .chdir ( home ); } } - void umount2 () { // ------------------------------- Lxroot umount2 - // someday I may move the the call to sys.umount2() to here. - return; } - - void xray () { // ------------------------------------- Lxroot xray if ( global_opt_trace == o_trace ) { @@ -2082,9 +2101,10 @@ class Lxroot : Lib { // xxlx ------------------------- class Lxroot printe ( "\n" ); printe ( "xray uid %d %d\n", getuid(), getgid() ); printe ( "xray cwd %s\n", getcwd().s ); - Argv ( st .env .data() ) .print ( "xray env " ); + printe ( "xray environment\n" ); + Argv ( st .env .data() ) .print ( "xray env " ); printe ( "xray command\n" ); - st .command .print ( "xray cmd " ); + st .command .print ( "xray cmd " ); printe ( "\n" ); printe ( "xray binds\n" ); @@ -2107,7 +2127,7 @@ class Lxroot : Lib { // xxlx ------------------------- class Lxroot unshare(); // before uid_map and bind uid_map(); // after unshare bind(); // after unshare - fgetpwent(); // after bind + fgetpwent(); // after bind, before expose expose(); // after fgetpwent, before remount remount(); // after expose options(); // late, before pivot, after remount @@ -2119,6 +2139,7 @@ class Lxroot : Lib { // xxlx ------------------------- class Lxroot /* pivot to umount2 should be as tight as possible */ env(); // after pivot, therefore after umount2 chdir(); // after umount2 ( because put_old may shadow $HOME !! ) + xray(); exec(); // last return 1; } // exec failed! diff --git a/readme.md b/readme.md index 13cbad4..f4e37a0 100644 --- a/readme.md +++ b/readme.md @@ -4,22 +4,22 @@ Lxroot is a lightweight alternative to chroot, Docker, and other software virtualization tools. -Lxroot allows a non-root user to quickly and easily create "chroot-style" virtual software environments (Linux namespaces), and then run one or more programs (a "guest userland") inside those namespaces. +Lxroot allows a non-root user to quickly and easily create a "chroot-style" virtual software environment (via Linux namespaces), and then run one or more programs (a "guest userland") inside that environment. For example, with Lxroot a non-root user can... - simultaneously run multiple, different guest userlands on a single Linux host -- run, as just one example, an Arch Linux userland on an Ubuntu Linux host +- run, for example, an Arch Linux userland on an Ubuntu Linux host - run a legacy userland on a modern host - run software in an "altered version" of the host system itself - run any given graphical X11 client (from any guest userland) on the host's X11 server -- create clean, controlled, and isolated guest environments for installing, building, and/or running software packages +- create a custom userland for developing software and/or building software packages +- control (or test) the installation (or upgrade) of software packages, as a non-root user, by installing and running the software in an isolated and easily disposable userland +- manage userlands with standard CLI tools: clone them with `rsync`, archive them with `tar` or `mksquashfs`, delete them `rm -rf` - deny software access to the network - restrict read and/or write access to specific directories - share one or more directories between an Lxroot environment and the host system - share one or more directories between multiple Lxroot environments. -- clone a userland with `rsync`; archive a userland with `tar` -- "upgrade" software by building a new, fresh userland, rather than attempting an in-place upgrade All without root access! @@ -200,7 +200,7 @@ You may also wish to look at the `demo.sh` file. Below is the output of `lxroot --help-more`: - usage: lxroot [mode] newroot [options] [--] [command [arg ...] ] + usage: lxroot [[mode] newroot] [options] [--] [command [arg ...] ] options -short one or more short options @@ -223,6 +223,7 @@ Below is the output of `lxroot --help-more`: SHORT OPTIONS + e import (almost) all external environment variables n allow network access (CLONE_NEWNET = 0) r simulate root user (map uid and gid to zero) w allow full write access to all read-auto binds @@ -230,6 +231,7 @@ Below is the output of `lxroot --help-more`: LONG OPTIONS + --env import (almost) all external environment variables --help display help --help-more display more help --network allow network access (CLONE_NEWNET = 0) @@ -281,9 +283,13 @@ Below is the output of `lxroot --help-more`: Note that the newroot, full-overlay, and partial-overlay options all have the same form, namely: [mode] path - The first option of this form is the newroot option. The newroot + The first option of this form is the newroot-option. The newroot- option specfies the newroot. + If no newroot-option is specified, then lxroot will neither bind, + chroot, nor pivot. This is useful to simulate root or deny network + access while retaining the current mount namespace. + FULL OVERLAY Zero or more full-overlay options may occur anywhere before the first diff --git a/unit.sh b/unit.sh index 47109d6..4d6cf56 100644 --- a/unit.sh +++ b/unit.sh @@ -23,7 +23,10 @@ run () { # -------------------------------------------------------- run stdout=` "${command[@]}" 2>&1 ` ; status=$? pretty="$status" set -o errexit - if [ "$status" = '0' ] ; then actual="$stdout" pretty=' ' + + if [ "$status" = '0' ] && + [ "$expect" = 'err 0-' ] ; then actual='err 0-' pretty=' ' + elif [ "$status" = '0' ] ; then actual="$stdout" pretty=' ' elif [ "$status" = '1' ] && [ "$expect" = 'err 1-' ] ; then actual='err 1-' elif [ "$stdout" ] ; then actual="err $status $stdout" @@ -219,8 +222,10 @@ test_no_home () { # -------------------------------------- test_no_home # test various newroots lxr1=() - run1 '' /bin/sh -c './lxr 2>/dev/null' - run1 'err 1 lxroot directory not found bad' ./lxr bad -- true + + run1 'err 0-' ./lxr + run1 'err 1 lxroot error execve bad No such file or directory' \ + ./lxr bad -- true return ; } @@ -567,6 +572,32 @@ test_options () { # -------------------------------------- test_options return ; } +test_no_newroot () { # -------------------------------- test_no_newroot + + echo ; echo "- test_no_newroot" + + env1=( env - ) lxr1=() cmd1=() + + run1 '' ./lxr -- true + run1 'err 1-' ./lxr -- nr + run1 "$UID" ./lxr -- id -u + run1 '0' ./lxr -r -- id -u + + return ; } + + +test_env () { # ---------------------------------------------- test_env + + echo ; echo "- test_env" + + env1=( env - 'FOO=BAR2' ) lxr1=() cmd1=() + + run1 'hello' ./lxr -e -- /bin/sh -c 'echo hello' + run1 'BAR2' ./lxr -e -- /bin/sh -c 'echo "$FOO"' + + return ; } + + main () { # ------------------------------------------------------ main unit="$1" @@ -581,6 +612,9 @@ main () { # ------------------------------------------------------ main cd "$unit" + test_no_newroot + test_env + test_no_home test_home test_readauto