diff options
| author | Roman Smrž <roman.smrz@seznam.cz> | 2025-07-05 18:15:06 +0200 | 
|---|---|---|
| committer | Roman Smrž <roman.smrz@seznam.cz> | 2025-07-06 13:03:19 +0200 | 
| commit | 28a93e24f6a33a8254c16c31961d523c71bdb1d2 (patch) | |
| tree | dfd54319c67c031cadf3398dc93ac648dc7e90be /src | |
| parent | 8e4bacb750d6b3657e5e8c72a8f30f14455812e5 (diff) | |
Isolate filesystems using mount namespace
Recursively bind and set to read-only all the host filesystems and
bind-mount as read-write only the test dir. Provide new writable tmpfs
under /tmp.
Changelog: Make host filesystems read-only for the test process (except for test dir)
Diffstat (limited to 'src')
| -rw-r--r-- | src/Run.hs | 21 | ||||
| -rw-r--r-- | src/Sandbox.hs | 16 | ||||
| -rw-r--r-- | src/main.c | 112 | 
3 files changed, 142 insertions, 7 deletions
| @@ -39,6 +39,7 @@ import Output  import Parser  import Process  import Run.Monad +import Sandbox  import Script.Expr  import Script.Module  import Script.Object @@ -102,11 +103,21 @@ runTest out opts gdefs test = do      oldHandler <- installHandler processStatusChanged (CatchInfo sigHandler) Nothing      resetOutputTime out -    ( res, [] ) <- runWriterT $ runExceptT $ flip runReaderT (tenv, tstate) $ fromTestRun $ do -        withInternet $ \_ -> do -            runStep =<< eval (testSteps test) -            when (optWait opts) $ do -                void $ outPromptGetLine $ "Test '" <> testName test <> "' completed, waiting..." +    testRunResult <- newEmptyMVar + +    void $ forkOS $ do +        isolateFilesystem testDir >>= \case +            True -> do +                tres <- runWriterT $ runExceptT $ flip runReaderT (tenv, tstate) $ fromTestRun $ do +                    withInternet $ \_ -> do +                        runStep =<< eval (testSteps test) +                        when (optWait opts) $ do +                            void $ outPromptGetLine $ "Test '" <> testName test <> "' completed, waiting..." +                putMVar testRunResult tres +            _ -> do +                putMVar testRunResult ( Left Failed, [] ) + +    ( res, [] ) <- takeMVar testRunResult      void $ installHandler processStatusChanged oldHandler Nothing diff --git a/src/Sandbox.hs b/src/Sandbox.hs new file mode 100644 index 0000000..a05a455 --- /dev/null +++ b/src/Sandbox.hs @@ -0,0 +1,16 @@ +module Sandbox ( +    isolateFilesystem, +) where + +import Foreign.C.String +import Foreign.C.Types + +import System.Directory + + +isolateFilesystem :: FilePath -> IO Bool +isolateFilesystem rwDir = do +    absDir <- makeAbsolute rwDir +    withCString absDir c_isolate_fs >>= return . (== 0) + +foreign import ccall unsafe "erebos_tester_isolate_fs" c_isolate_fs :: CString -> IO CInt @@ -9,8 +9,11 @@  #include <sched.h>  #include <stdbool.h>  #include <stdio.h> +#include <stdlib.h>  #include <string.h>  #include <sys/mount.h> +#include <sys/stat.h> +#include <sys/syscall.h>  #include <unistd.h>  /* @@ -45,9 +48,15 @@ static bool writeProcSelfFile( const char * file, const char * data, size_t size  int main( int argc, char * argv[] )  { +	int ret; +  	uid_t uid = geteuid();  	gid_t gid = getegid(); -	unshare( CLONE_NEWUSER | CLONE_NEWNET | CLONE_NEWNS ); +	ret = unshare( CLONE_NEWUSER | CLONE_NEWNET | CLONE_NEWNS ); +	if( ret < 0 ){ +		fprintf( stderr, "unsharing user, network and mount namespaces failed: %s\n", strerror( errno )); +		return 1; +	}  	char buf[ 256 ];  	int len; @@ -71,7 +80,63 @@ int main( int argc, char * argv[] )  	if ( ! writeProcSelfFile( "gid_map", buf, len ) )  		return 1; -	mount( "tmpfs", "/run", "tmpfs", 0, "size=4m" ); +	/* +	 * Prepare for future filesystem isolation within additional mount namespace: +	 * - clone whole mount tree as read-only under new /tmp/new_root +	 * - keep writable /proc and /tmp +	 */ + +	ret = mount( "tmpfs", "/run", "tmpfs", 0, "size=4m" ); +	if( ret < 0 ){ +		fprintf( stderr, "failed to mount tmpfs on /run: %s\n", strerror( errno )); +		return 1; +	} + +	ret = mkdir( "/run/new_root", 0700 ); +	if( ret < 0 ){ +		fprintf( stderr, "failed to create new_root directory: %s\n", strerror( errno )); +		return 1; +	} + +	ret = mount( "/", "/run/new_root", NULL, MS_BIND | MS_REC,  NULL ); +	if( ret < 0 ){ +		fprintf( stderr, "failed to bind-mount / on new_root: %s\n", strerror( errno )); +		return 1; +	} + +	struct mount_attr * attr_ro = &( struct mount_attr ) { +		.attr_set = MOUNT_ATTR_RDONLY, +	}; +	ret = mount_setattr( -1, "/run/new_root", AT_RECURSIVE, attr_ro, sizeof( * attr_ro ) ); +	if( ret < 0 ){ +		fprintf( stderr, "failed set new_root as read-only: %s\n", strerror( errno )); +		return 1; +	} + +	struct mount_attr * attr_rw = &( struct mount_attr ) { +		.attr_clr = MOUNT_ATTR_RDONLY, +	}; +	ret = mount_setattr( -1, "/run/new_root/proc", AT_RECURSIVE, attr_rw, sizeof( * attr_rw ) ); +	if( ret < 0 ){ +		fprintf( stderr, "failed set new_root/proc as read-write: %s\n", strerror( errno )); +		return 1; +	} +	ret = mount_setattr( -1, "/run/new_root/tmp", AT_RECURSIVE, attr_rw, sizeof( * attr_rw ) ); +	if( ret < 0 ){ +		fprintf( stderr, "failed set new_root/tmp as read-write: %s\n", strerror( errno )); +	} + +	ret = mount( "tmpfs", "/run/new_root/run", "tmpfs", 0, "size=4m" ); +	if( ret < 0 ){ +		fprintf( stderr, "failed to mount tmpfs on new_root/run: %s\n", strerror( errno )); +		return 1; +	} + +	ret = mkdir( "/run/new_root/run/old_root", 0700 ); +	if( ret < 0 ){ +		fprintf( stderr, "failed to create old_root directory: %s\n", strerror( errno )); +		return 1; +	}  	hs_init( &argc, &argv );  	testerMain(); @@ -79,3 +144,46 @@ int main( int argc, char * argv[] )  	return 0;  } + +/* + * - Replace filesystem hierarchy with read-only version, + * - bind-mound rwdir from writable tree, and + * - keep writeable /tmp from host. + */ +int erebos_tester_isolate_fs( const char * rwdir ) +{ +	int ret; + +	ret = unshare( CLONE_NEWNS ); +	if( ret < 0 ){ +		fprintf( stderr, "unsharing mount namespace failed: %s\n", strerror( errno )); +		return -1; +	} + +	char * cwd = getcwd( NULL, 0 ); +	ret = syscall( SYS_pivot_root, "/run/new_root", "/run/new_root/run/old_root" ); +	if( ret < 0 ){ +		fprintf( stderr, "failed to pivot_root: %s\n", strerror( errno )); +		free( cwd ); +		return -1; +	} + +	char oldrwdir[ strlen(rwdir) + 15 ]; +	snprintf( oldrwdir, sizeof oldrwdir, "/run/old_root/%s", rwdir ); +	ret = mount( oldrwdir, rwdir, NULL, MS_BIND, NULL ); +	if( ret < 0 ){ +		fprintf( stderr, "failed to bind-mount %s on %s: %s\n", oldrwdir, rwdir, strerror( errno )); +		free( cwd ); +		return -1; +	} + +	ret = chdir( cwd ); +	if( ret < 0 ){ +		fprintf( stderr, "failed to chdir to %s: %s\n", cwd, strerror( errno )); +		free( cwd ); +		return -1; +	} +	free( cwd ); + +	return 0; +} |