diff --git a/os/run/chroot_loader.run b/os/run/chroot_loader.run
new file mode 100644
index 0000000000..e7dfc0575a
--- /dev/null
+++ b/os/run/chroot_loader.run
@@ -0,0 +1,139 @@
+#
+# \brief Test for using chroot on Linux
+# \author Norman Feske
+# \date 2012-06-06
+#
+#
+if {![have_spec linux]} { puts "Run script requires Linux"; exit 0 }
+
+#
+# Build
+#
+
+build { core init app/chroot drivers/timer/linux test/timer
+ server/loader test/chroot_loader }
+
+if {[catch { exec which setcap }]} {
+ puts stderr "Error: setcap not available, please install the libcap2-bin package"
+ return 0
+}
+
+
+create_boot_directory
+
+#
+# Generate config
+#
+
+set config {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+}
+
+proc chroot_path { id } { return "/tmp/chroot-test-$id" }
+
+proc chroot_cwd_path { id } { return "[chroot_path $id][pwd]/[run_dir]" }
+
+proc chroot_genode_tmp_path { id } { return "[chroot_path $id]/tmp/genode-[exec id -u]" }
+
+proc cleanup_chroot { } {
+ foreach id { 1 2 } {
+ catch { exec sudo umount -l [chroot_cwd_path $id] }
+ catch { exec sudo umount -l [chroot_genode_tmp_path $id] }
+ catch { exec sudo umount -l [chroot_path $id]/lib }
+ catch { exec rm -rf [chroot_path $id] }
+ }
+}
+
+# replace 'chroot_path' markers in config with actual paths
+foreach id { 1 2 } {
+ regsub "chroot_path_$id" $config [chroot_path $id] config }
+
+install_config $config
+
+#
+# Copy boot modules into run directory
+#
+# We cannot use the predefined 'build_boot_image' function here because
+# this would create mere symlinks. However, we want to hardlink the
+# run directory into the chroot environment. If the directory entries
+# were symlinks, those would point to nowhere within the chroot.
+#
+foreach binary { core init chroot timer loader test-chroot_loader test-timer} {
+ exec cp -H bin/$binary [run_dir] }
+
+#
+# Grant chroot permission to 'chroot' tool
+#
+# CAP_SYS_ADMIN is needed for bind mounting genode runtime directories
+# CAP_SYS_CHROOT is needed to perform the chroot syscall
+#
+exec sudo setcap cap_sys_admin,cap_sys_chroot=ep [run_dir]/chroot
+
+#
+# Setup chroot environment
+#
+
+# start with fresh directory
+cleanup_chroot
+foreach id { 1 2 } {
+ exec mkdir -p [chroot_path $id]
+ exec mkdir -p [chroot_path $id]/lib
+
+ # bind mount '/lib' as need libc within the chroot environment
+ exec sudo mount --bind /lib [chroot_path $id]/lib
+}
+
+#
+# Execute test case
+#
+run_genode_until {.*--- chroot-loader test finished ---\s*\n} 60
+
+#
+# Validate log output
+#
+
+if {[regexp -all -- {--- timer test ---} $output] != 6} {
+ puts stderr "Number of spawned subsystems differs from 6"
+ exit 2
+}
+
+if {![regexp -- {chroot-1 -> test-timer] wait 2/10} $output]} {
+ puts stderr "Long-running timer test has made too little progress"
+ exit 3
+}
+
+#
+# Remove artifacts created while running the test
+#
+cleanup_chroot
+
+# vi: set ft=tcl :
diff --git a/os/src/test/chroot_loader/main.cc b/os/src/test/chroot_loader/main.cc
new file mode 100644
index 0000000000..8b2fce76c5
--- /dev/null
+++ b/os/src/test/chroot_loader/main.cc
@@ -0,0 +1,174 @@
+/*
+ * \brief Test for dynamically starting chrooted subsystems via the loader
+ * \author Norman Feske
+ * \date 2012-06-06
+ *
+ * This test creates two subsystems, each residing in a dedicated chroot
+ * environment, by combining the loader service with the chroot mechanism.
+ * One subsystem runs infinitely. The other subsystem will be repeatedly
+ * started and killed.
+ */
+
+/* Genode includes */
+#include
+#include
+#include
+#include
+
+
+/*******************************************************
+ ** Helpers for obtaining test parameters from config **
+ *******************************************************/
+
+static char const *chroot_path_from_config(char const *node_name,
+ char *dst, Genode::size_t dst_len)
+{
+ Genode::config()->xml_node().sub_node(node_name)
+ .attribute("chroot_path").value(dst, dst_len);
+ return dst;
+}
+
+
+static char const *chroot_path_of_static_test()
+{
+ static char buf[1024];
+ return chroot_path_from_config("static_test", buf, sizeof(buf));
+}
+
+
+static char const *chroot_path_of_dynamic_test()
+{
+ static char buf[1024];
+ return chroot_path_from_config("dynamic_test", buf, sizeof(buf));
+}
+
+
+/**********
+ ** Test **
+ **********/
+
+/**
+ * Return format string used as template for the subsystem configuration.
+ *
+ * Note the format-string argument used for inserting the chroot path.
+ */
+static char const *config_template()
+{
+ return "\n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ "\n";
+}
+
+
+/**
+ * Chroot subsystem corresponding to a loader session
+ */
+class Chroot_subsystem
+{
+ private:
+
+ Loader::Connection _loader;
+
+ char _label[32];
+
+ /**
+ * Import data as ROM module into the subsystem-specific ROM service
+ */
+ void _import_rom_module(char const *name, void *ptr, Genode::size_t size)
+ {
+ using namespace Genode;
+
+ Dataspace_capability ds = _loader.alloc_rom_module(name, size);
+
+ /* fill ROM module with data */
+ char *local_addr = env()->rm_session()->attach(ds);
+ memcpy(local_addr, ptr, size);
+ env()->rm_session()->detach(local_addr);
+
+ _loader.commit_rom_module(name);
+ }
+
+ public:
+
+ Chroot_subsystem(char const *chroot_path, Genode::size_t ram_quota)
+ :
+ _loader(ram_quota)
+ {
+ using namespace Genode;
+
+ /*
+ * Generate Genode configuration of the new subsystem and import
+ * it into the subsystem's loader session as a ROM module named
+ * "config".
+ */
+ char buf[strlen(chroot_path) + strlen(config_template()) + 1];
+ snprintf(buf, sizeof(buf), config_template(), chroot_path);
+
+ _import_rom_module("config", buf, strlen(buf) + 1);
+
+ /*
+ * Name of the Genode binary is start as the root of the new
+ * subsystem.
+ */
+ char const *chroot_binary_name = "chroot";
+
+ /*
+ * Generate unique label name using a counter
+ *
+ * The label appears in the LOG output of the loaded subsystem.
+ * Technically, it does need to be unique. It is solely used
+ * for validating the test in the run script.
+ */
+ static int cnt = 0;
+ snprintf(_label, sizeof(_label), "%s-%d", chroot_binary_name, ++cnt);
+
+ /* start execution of new subsystem */
+ _loader.start(chroot_binary_name, Loader::Session::Name(_label));
+ }
+};
+
+
+int main(int, char **)
+{
+ Genode::printf("--- chroot-loader test started ---\n");
+
+ static Chroot_subsystem static_subsystem(chroot_path_of_static_test(),
+ 2*1024*1024);
+
+ static Timer::Connection timer;
+
+ for (unsigned i = 0; i < 5; i++) {
+
+ PLOG("dynamic test iteration %d", i);
+
+ Chroot_subsystem subsystem(chroot_path_of_dynamic_test(),
+ 2*1024*1024);
+
+ /* grant the subsystem one second of life */
+ timer.msleep(1000);
+
+ /*
+ * The local 'dynamic_subsystem' instance will be destructed at the of
+ * the loop body.
+ */
+ }
+
+ Genode::printf("--- chroot-loader test finished ---\n");
+ return 0;
+}
diff --git a/os/src/test/chroot_loader/target.mk b/os/src/test/chroot_loader/target.mk
new file mode 100644
index 0000000000..b59705e964
--- /dev/null
+++ b/os/src/test/chroot_loader/target.mk
@@ -0,0 +1,3 @@
+TARGET = test-chroot_loader
+SRC_CC = main.cc
+LIBS += cxx env