Skip to content

Commit

Permalink
selftests/bpf: experiment with mnt namespaces and SCM_RIGHTS
Browse files Browse the repository at this point in the history
  $ sudo ./test_progs -t mntns -v
  ...
  main(46905): mnt:[4026531841]
  Mount FD1: 5
  Mount FD2: 7
  WAITING FOR CHILDREN...
  child1(46909) STARTED
  child2(46910) STARTED
  child1(46909): SENT FD 5 to SOCK 8
  child1(46909): GOT FD 10 from SOCK 8
  child2(46910): SENT FD 7 to SOCK 9
  child2(46910): GOT FD 10 from SOCK 9
  child1(46909): mnt:[4026533571]
  child1(46909): user:[4026533573]
  child1(46909): 1028 990 0:85 / /sys/kernel/debug rw,relatime - tmpfs none rw,inode64
  child1(46909): 444 1028 0:51 / /sys/kernel/debug/A rw,relatime - bpf none rw
  5 vs 10
  pos:    0
  flags:  010000000
  mnt_id: 444
  ino:    1
  child1(46909): pos:     0
  child1(46909): flags:   010000000
  child1(46909): mnt_id:  978
  child1(46909): ino:     1
  child1(46909): total 0
  child1(46909): drwxrwxrwt 6 nfsnobody nfsnobody  0 Jun 20 17:19 .
  child1(46909): drwxrwxrwt 3 nfsnobody nfsnobody 60 Jun 20 17:19 ..
  child1(46909): -r-------- 1 nfsnobody nfsnobody  0 Jun 20 17:19 maps.debug
  child1(46909): -r-------- 1 nfsnobody nfsnobody  0 Jun 20 17:19 progs.debug
  child1(46909): drwxr-xr-x 2 nfsnobody nfsnobody  0 Jun 20 17:19 SUBA
  child1(46909): drwxr-xr-x 2 nfsnobody nfsnobody  0 Jun 20 17:19 SUBAA
  child1(46909): drwxr-xr-x 2 nfsnobody nfsnobody  0 Jun 20 17:19 SUBBBB
  child1(46909): drwxr-xr-x 2 nfsnobody nfsnobody  0 Jun 20 17:19 UDS_BBBB
  child1(46909) EXITING
  child2(46910): mnt:[4026533572]
  child2(46910): user:[4026533574]
  child2(46910): 1113 2347 0:88 / /sys/kernel/debug rw,relatime - tmpfs none rw,inode64
  child2(46910): 978 1113 0:75 / /sys/kernel/debug/B rw,relatime - bpf none rw
  7 vs 10
  pos:    0
  flags:  010000000
  mnt_id: 978
  ino:    1
  child2(46910): pos:     0
  child2(46910): flags:   010000000
  child2(46910): mnt_id:  444
  child2(46910): ino:     1
  child2(46910): total 0
  child2(46910): drwxrwxrwt 6 nfsnobody nfsnobody  0 Jun 20 17:19 .
  child2(46910): drwxrwxrwt 3 nfsnobody nfsnobody 60 Jun 20 17:19 ..
  child2(46910): -r-------- 1 nfsnobody nfsnobody  0 Jun 20 17:19 maps.debug
  child2(46910): -r-------- 1 nfsnobody nfsnobody  0 Jun 20 17:19 progs.debug
  child2(46910): drwxr-xr-x 2 nfsnobody nfsnobody  0 Jun 20 17:19 SUBAAA
  child2(46910): drwxr-xr-x 2 nfsnobody nfsnobody  0 Jun 20 17:19 SUBB
  child2(46910): drwxr-xr-x 2 nfsnobody nfsnobody  0 Jun 20 17:19 SUBBB
  child2(46910): drwxr-xr-x 2 nfsnobody nfsnobody  0 Jun 20 17:19 UDS_AAAA
  child2(46910) EXITING
  MAIN EXITING...
  torvalds#138     mntns:OK
  Summary: 1/0 PASSED, 0 SKIPPED, 0 FAILED

Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
  • Loading branch information
anakryiko committed Jun 21, 2023
1 parent 970308a commit a219414
Showing 1 changed file with 392 additions and 0 deletions.
392 changes: 392 additions & 0 deletions tools/testing/selftests/bpf/prog_tests/mntns.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,392 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2023 Meta Platforms, Inc. and affiliates. */
#define _GNU_SOURCE
#include <test_progs.h>
#include <bpf/btf.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/unistd.h>
//#include <linux/mount.h>
#include <sys/syscall.h>
#include <sys/mount.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sched.h>
#include <sys/socket.h>
#include <sys/un.h>

enum fsconfig_command {
FSCONFIG_SET_FLAG = 0, /* Set parameter, supplying no value */
FSCONFIG_SET_STRING = 1, /* Set parameter, supplying a string value */
FSCONFIG_SET_BINARY = 2, /* Set parameter, supplying a binary blob value */
FSCONFIG_SET_PATH = 3, /* Set parameter, supplying an object by path */
FSCONFIG_SET_PATH_EMPTY = 4, /* Set parameter, supplying an object by (empty) path */
FSCONFIG_SET_FD = 5, /* Set parameter, supplying an object by fd */
FSCONFIG_CMD_CREATE = 6, /* Invoke superblock creation */
FSCONFIG_CMD_RECONFIGURE = 7, /* Invoke superblock reconfiguration */
};


__attribute__((unused))
static inline int sys_fsopen(const char *fsname, unsigned flags)
{
return syscall(__NR_fsopen, fsname, flags);
}

__attribute__((unused))
static inline int sys_fsconfig(int fs_fd, unsigned cmd, const char *key, const void *val, int aux)
{
return syscall(__NR_fsconfig, fs_fd, cmd, key, val, aux);
}

__attribute__((unused))
static inline int sys_fsmount(int fs_fd, unsigned flags, unsigned ms_flags)
{
return syscall(__NR_fsmount, fs_fd, flags, ms_flags);
}

__attribute__((unused))
static inline int sys_move_mount(int from_dfd, const char *from_path,
int to_dfd, const char *to_path,
unsigned int ms_flags)
{
return syscall(__NR_move_mount, from_dfd, from_path, to_dfd, to_path, ms_flags);
}

__attribute__((unused))
static inline int sys_open_tree(int dfd, const char *filename, unsigned flags)
{
return syscall(__NR_open_tree, dfd, filename, flags);
}

#define TDIR "/sys/kernel/debug"

static char tname[32];

static int sendfd(int sockfd, int fd)
{
struct msghdr msg = { 0 };
struct cmsghdr *cmsg;
int fds[1] = { fd };
char iobuf[1];
struct iovec io = {
.iov_base = iobuf,
.iov_len = sizeof(iobuf),
};
union {
char buf[CMSG_SPACE(sizeof(fds))];
struct cmsghdr align;
} u;

msg.msg_iov = &io;
msg.msg_iovlen = 1;
msg.msg_control = u.buf;
msg.msg_controllen = sizeof(u.buf);
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(fds));
memcpy(CMSG_DATA(cmsg), fds, sizeof(fds));

if (sendmsg(sockfd, &msg, 0) < 0) {
fprintf(stderr, "%s: failed to send fd %d over sock %d: %d\n", tname, fd, sockfd, -errno);
return -errno;
}

fprintf(stderr, "%s: SENT FD %d to SOCK %d\n", tname, fd, sockfd);

return 0;
}

static int recvfd(int sockfd, int *fd)
{
struct msghdr msg = { 0 };
struct cmsghdr *cmsg;
int fds[1];
char iobuf[1];
struct iovec io = {
.iov_base = iobuf,
.iov_len = sizeof(iobuf),
};
union {
char buf[CMSG_SPACE(sizeof(fds))];
struct cmsghdr align;
} u;

msg.msg_iov = &io;
msg.msg_iovlen = 1;
msg.msg_control = u.buf;
msg.msg_controllen = sizeof(u.buf);

if (recvmsg(sockfd, &msg, 0) < 0) {
fprintf(stderr, "%s: failed to recv fd from sock %d: %d\n", tname, sockfd, -errno);
return -errno;
}

cmsg = CMSG_FIRSTHDR(&msg);
if (cmsg == NULL
|| cmsg->cmsg_len != CMSG_LEN(sizeof(fds))
|| cmsg->cmsg_level != SOL_SOCKET
|| cmsg->cmsg_type != SCM_RIGHTS) {
fprintf(stderr, "%s: invalid cmsg received!\n", tname);
return -EINVAL;
}

memcpy(fds, CMSG_DATA(cmsg), sizeof(fds));
*fd = fds[0];

fprintf(stderr, "%s: GOT FD %d from SOCK %d\n", tname, *fd, sockfd);

return 0;
}


int fsfd1, fsfd2;
int mntfd1, mntfd2;
int sockfds[2];

static int create_bpffs(void)
{
fsfd1 = sys_fsopen("bpf", 0);
sys_fsconfig(fsfd1, FSCONFIG_CMD_CREATE, NULL, NULL, 0);
mntfd1 = sys_fsmount(fsfd1, 0, 0);
if (mntfd1 < 0) {
fprintf(stderr, "%s: failed to create fs1: %d\n", tname, -errno);
return -errno;
}
fprintf(stderr, "Mount FD1: %d\n", mntfd1);

fsfd2 = sys_fsopen("bpf", 0);
sys_fsconfig(fsfd2, FSCONFIG_CMD_CREATE, NULL, NULL, 0);
mntfd2 = sys_fsmount(fsfd2, 0, 0);
if (mntfd2 < 0) {
fprintf(stderr, "%s: failed to create fs2: %d\n", tname, -errno);
return -errno;
}
fprintf(stderr, "Mount FD2: %d\n", mntfd2);
return 0;
}

#define FSPICK_CLOEXEC 0x00000001
#define FSPICK_SYMLINK_NOFOLLOW 0x00000002
#define FSPICK_NO_AUTOMOUNT 0x00000004
#define FSPICK_EMPTY_PATH 0x00000008

#define MOVE_MOUNT_F_SYMLINKS 0x00000001 /* Follow symlinks on from path */
#define MOVE_MOUNT_F_AUTOMOUNTS 0x00000002 /* Follow automounts on from path */
#define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 /* Empty from path permitted */
#define MOVE_MOUNT_T_SYMLINKS 0x00000010 /* Follow symlinks on to path */
#define MOVE_MOUNT_T_AUTOMOUNTS 0x00000020 /* Follow automounts on to path */
#define MOVE_MOUNT_T_EMPTY_PATH 0x00000040 /* Empty to path permitted */
#define MOVE_MOUNT_SET_GROUP 0x00000100 /* Set sharing group instead */
#define MOVE_MOUNT__MASK 0x00000177

#define OPEN_TREE_CLONE 1 /* Clone the target tree and attach the clone */
#define OPEN_TREE_CLOEXEC O_CLOEXEC /* Close the file on execve() */


__attribute__((unused))
static void rdlink(const char *path)
{
char buf[256];

snprintf(buf, sizeof(buf), "readlink %s | sed -e 's/^/%s: /'", path, tname);
system(buf);
}

__attribute__((unused))
static void cat(const char *path)
{
char buf[256];

snprintf(buf, sizeof(buf), "cat %s | sed -e 's/^/%s: /'", path, tname);
system(buf);
}

__attribute__((unused))
static void cmd(const char *cmd, ...)
{
va_list args;
char buf[512], tmp[256];

va_start(args, cmd);
vsnprintf(tmp, sizeof(tmp), cmd, args);
va_end(args);

snprintf(buf, sizeof(buf), "%s | sed -e 's/^/%s: /'", tmp, tname);
system(buf);
}

static int prep_mntns(bool first)
{
int err, mntfd, clonefd = -1;

err = unshare(CLONE_NEWNS);
if (err) {
fprintf(stderr, "%s: Failed to unshare mnt\n", tname);
return -EINVAL;
}
err = mount("", "/", "", MS_REC | MS_PRIVATE, NULL);
if (err) {
fprintf(stderr, "%s: Failed to remount\n", tname);
return -EINVAL;
}
err = umount(TDIR);
if (err) {
fprintf(stderr, "%s: Failed to umount /tmp\n", tname);
return -EINVAL;
}
err = err ?: mount("none", TDIR, "tmpfs", 0, NULL);
if (err) {
fprintf(stderr, "%s: Failed to mount tmpfs\n", tname);
return -EINVAL;
}
if (first) {
err = err ?: mkdir(TDIR "/A", 0777);
} else {
err = err ?: mkdir(TDIR "/B", 0777);
}
if (err) {
fprintf(stderr, "%s: Failed mkdir tmp subdir\n", tname);
return -EINVAL;
}
if (first) {
err = sys_move_mount(mntfd1, "", -EBADF, TDIR "/A", MOVE_MOUNT_F_EMPTY_PATH);
if (err) fprintf(stderr, "%s: Failed to move_mount %s: %d\n", tname, TDIR "/A", -errno);
} else {
err = sys_move_mount(mntfd2, "", -EBADF, TDIR "/B", MOVE_MOUNT_F_EMPTY_PATH);
if (err) fprintf(stderr, "%s: Failed to move_mount %s: %d\n", tname, TDIR "/B", -errno);
}
if (first) {
clonefd = sys_open_tree(-EBADF, TDIR "/A", OPEN_TREE_CLONE);
if (clonefd < 0) {
fprintf(stderr, "%s: Failed to open_tree() %d: %d\n", tname, mntfd1, -errno);
return -EINVAL;
}
}
sleep(1);
err = unshare(CLONE_NEWUSER);
if (err) {
fprintf(stderr, "%s: Failed to unshare user\n", tname);
return -EINVAL;
}
if (first) {
err = err ?: mkdir(TDIR "/A/SUBA", 0777);
err = err ?: mkdirat(mntfd1, "SUBAA", 0777);
err = err ?: mkdirat(mntfd2, "SUBAAA", 0777);
} else {
err = err ?: mkdir(TDIR "/B/SUBB", 0777);
err = err ?: mkdirat(mntfd2, "SUBBB", 0777);
err = err ?: mkdirat(mntfd1, "SUBBBB", 0777);
}
if (err) {
fprintf(stderr, "%s: Failed mkdir inside BPFFS\n", tname);
return -EINVAL;
}

sendfd(first ? sockfds[0] : sockfds[1], first ? mntfd1 : mntfd2);
recvfd(first ? sockfds[0] : sockfds[1], &mntfd);

err = mkdirat(mntfd, first ? "UDS_AAAA" : "UDS_BBBB", 0777);
if (err)
fprintf(stderr, "%s: Failed mkdir inside BPFFS received from UDS: %d\n", tname, -errno);

if (first) {
/* this WILL FAIL */
err = sys_move_mount(clonefd, "", mntfd, "UDS_AAAA", MOVE_MOUNT_F_EMPTY_PATH);
if (err) fprintf(stderr, "%s: Failed to move_mount into %s: %d\n", tname, "UDS_AAAA", -errno);
}

sleep(first ? 0 : 1);

rdlink("/proc/self/ns/mnt");
rdlink("/proc/self/ns/user");
cmd("cat /proc/self/mountinfo | grep " TDIR);
cmd("echo %d vs %d; cat /proc/self/fdinfo/%d; cat /proc/self/fdinfo/%d",
first ? mntfd1 : mntfd2, mntfd, first ? mntfd1 : mntfd2, mntfd);
//cmd("tree " TDIR);
if (first)
cmd("ls -la " TDIR "/A");
else
cmd("ls -la " TDIR "/B");

return 0;
}

static int child1_main(void)
{
int err;

sprintf(tname, "child1(%d)", getpid());
printf("%s STARTED\n", tname);

err = prep_mntns(true);
if (err) {
fprintf(stderr, "%s: Failed to prep mount namespace\n", tname);
return 1;
}


printf("%s EXITING\n", tname);
return 0;
}

static int child2_main(void)
{
int err;

sprintf(tname, "child2(%d)", getpid());
printf("%s STARTED\n", tname);

err = prep_mntns(false);
if (err) {
fprintf(stderr, "%s: Failed to prep mount namespace\n", tname);
return 1;
}

printf("%s EXITING\n", tname);
return 0;
}

void test_mntns()
{
int pid1, pid2;
int status, err;

sprintf(tname, "main(%d)", getpid());
rdlink("/proc/self/ns/mnt");

if (create_bpffs()) {
fprintf(stderr, "Failed to prep BPFFSs\n");
return;
}

err = socketpair(AF_UNIX, SOCK_STREAM, 0, sockfds);
if (err) {
fprintf(stderr, "Failed to create UDS socket pair: %d\n", -errno);
return;
}

pid1 = fork();
if (pid1 == 0)
exit(child1_main());
pid2 = fork();
if (pid2 == 0)
exit(child2_main());

printf("WAITING FOR CHILDREN...\n");

err = waitpid(pid1, &status, 0);
if (err < 0 && errno != ECHILD)
fprintf(stderr, "waitpid1 failed\n");
else if (WEXITSTATUS(status) != 0)
fprintf(stderr, "child1 failed: %d\n", WEXITSTATUS(status));

err = waitpid(pid2, &status, 0);
if (err < 0 && errno != ECHILD)
fprintf(stderr, "waitpid2 failed\n");
else if (WEXITSTATUS(status) != 0)
fprintf(stderr, "child2 failed: %d\n", WEXITSTATUS(status));

printf("MAIN EXITING...\n");
}

0 comments on commit a219414

Please sign in to comment.