Add regression test for nsfs

This commit is contained in:
Jianfeng Jiang 2026-02-11 11:16:20 +00:00
parent 9c99e782ed
commit 78f098fe88
2 changed files with 231 additions and 0 deletions

View File

@ -0,0 +1,230 @@
// SPDX-License-Identifier: MPL-2.0
#define _GNU_SOURCE
#include <dirent.h>
#include <fcntl.h>
#include <errno.h>
#include <limits.h>
#include <linux/nsfs.h>
#include <poll.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "../test.h"
#define NS_DIR "/proc/self/ns"
// ns_names contains the namespace names as they appear in readlink output.
// For pid_for_children and time_for_children, the readlink output shows "pid" and "time" respectively.
// For other namespaces, ns_files and ns_names have identical corresponding strings.
char *ns_files[] = { "uts", "mnt", "user" };
char *ns_names[] = { "uts", "mnt", "user" };
int clone_flags[] = { CLONE_NEWUTS, CLONE_NEWNS, CLONE_NEWUSER };
FN_TEST(common_fs_operations)
{
char path[PATH_MAX];
char buf[1];
size_t ns_count = sizeof(ns_files) / sizeof(ns_files[0]);
for (size_t i = 0; i < ns_count; i++) {
memset(path, 0, sizeof(path));
snprintf(path, sizeof(path), "%s/%s", NS_DIR, ns_files[i]);
TEST_ERRNO(open(path, O_RDWR), EPERM);
TEST_ERRNO(open(path, O_WRONLY), EPERM);
int nsfd = TEST_SUCC(open(path, O_RDONLY));
TEST_ERRNO(read(nsfd, buf, 1), EINVAL);
TEST_ERRNO(write(nsfd, buf, 1), EBADF);
struct pollfd ns_pfd = { .fd = nsfd,
.events = POLLIN | POLLOUT |
POLLRDHUP | POLLPRI |
POLLRDNORM };
TEST_RES(poll(&ns_pfd, 1, -1),
ns_pfd.revents == (POLLIN | POLLOUT | POLLRDNORM));
struct stat64 stat;
TEST_RES(fstat64(nsfd, &stat),
stat.st_mode == (0444 | S_IFREG));
TEST_ERRNO(lseek64(nsfd, 0, SEEK_SET), ESPIPE);
}
}
END_TEST()
FN_TEST(readlink)
{
char path[PATH_MAX];
char buf[256];
size_t ns_count = sizeof(ns_files) / sizeof(ns_files[0]);
for (size_t i = 0; i < ns_count; i++) {
memset(path, 0, sizeof(path));
memset(buf, 0, sizeof(buf));
snprintf(path, sizeof(path), "%s/%s", NS_DIR, ns_files[i]);
char expected_format[256] = { 0 };
int nsfd = TEST_SUCC(open(path, O_RDONLY));
struct stat stat;
TEST_SUCC(fstat(nsfd, &stat));
char *ns_name = ns_names[i];
snprintf(expected_format, sizeof(expected_format), "%s:[%u]",
ns_name, (unsigned int)stat.st_ino);
TEST_RES(readlink(path, buf, sizeof(buf) - 1),
strcmp(expected_format, buf) == 0);
}
}
END_TEST()
FN_TEST(zombie_process)
{
char path[PATH_MAX];
size_t ns_count = sizeof(ns_files) / sizeof(ns_files[0]);
pid_t pid = fork();
TEST_RES(pid >= 0, 1);
if (pid == 0) {
// Child process exits immediately to become zombie
exit(0);
}
TEST_SUCC(waitid(P_PID, pid, NULL, WNOWAIT | WEXITED));
// Try to access zombie process's namespace files
for (size_t i = 0; i < ns_count; i++) {
char *ns_file = ns_files[i];
snprintf(path, sizeof(path), "/proc/%d/ns/%s", pid,
ns_files[i]);
// Should still be able to open zombie's PID/user namespace files
if (strcmp(ns_file, "pid") == 0 ||
strcmp(ns_file, "user") == 0) {
char buf[256] = { 0 };
TEST_SUCC(readlink(path, buf, sizeof(buf) - 1));
int nsfd = TEST_SUCC(open(path, O_RDONLY));
TEST_SUCC(close(nsfd));
} else {
TEST_ERRNO(open(path, O_RDONLY), ENOENT);
}
}
TEST_SUCC(waitpid(pid, NULL, 0));
}
END_TEST()
FN_TEST(ioctl)
{
char path[PATH_MAX];
for (size_t i = 0; i < sizeof(ns_files) / sizeof(ns_files[0]); i++) {
snprintf(path, sizeof(path), "%s/%s", NS_DIR, ns_files[i]);
int nsfd = TEST_SUCC(open(path, O_RDONLY));
// NS_GET_USERNS
if (strcmp(ns_files[i], "user") != 0) {
int userns_fd = TEST_SUCC(ioctl(nsfd, NS_GET_USERNS));
TEST_SUCC(close(userns_fd));
} else {
TEST_ERRNO(ioctl(nsfd, NS_GET_USERNS), EPERM);
}
// NS_GET_PARENT - get parent namespace (should fail for non-hierarchical ns)
if (strcmp(ns_files[i], "user") != 0) {
TEST_ERRNO(ioctl(nsfd, NS_GET_PARENT), EINVAL);
} else {
TEST_ERRNO(ioctl(nsfd, NS_GET_PARENT), EPERM);
}
// NS_GET_NSTYPE
TEST_RES(ioctl(nsfd, NS_GET_NSTYPE), _ret == clone_flags[i]);
// NS_GET_OWNER_UID: User namespace only
if (strcmp(ns_files[i], "user") != 0) {
TEST_ERRNO(ioctl(nsfd, NS_GET_OWNER_UID), EINVAL);
} else {
TEST_ERRNO(ioctl(nsfd, NS_GET_OWNER_UID, 0), EFAULT);
uid_t uid;
TEST_SUCC(ioctl(nsfd, NS_GET_OWNER_UID, &uid));
}
}
}
END_TEST()
FN_TEST(setns)
{
char path[PATH_MAX];
// Test setns with current process's namespace (should succeed)
for (size_t i = 0; i < sizeof(ns_files) / sizeof(ns_files[0]); i++) {
snprintf(path, sizeof(path), "%s/%s", NS_DIR, ns_files[i]);
int nsfd = TEST_SUCC(open(path, O_RDONLY));
// setns to own namespace should succeed
if (strcmp(ns_files[i], "user") != 0) {
TEST_SUCC(setns(nsfd, 0));
} else {
TEST_ERRNO(setns(nsfd, 0), EINVAL);
}
TEST_SUCC(close(nsfd));
}
// Test setns with invalid fd
TEST_ERRNO(setns(-1, 0), EBADF);
// Test setns across fork
pid_t pid = fork();
TEST_RES(pid >= 0, 1);
if (pid == 0) {
// Child: try to join parent's UTS namespace
snprintf(path, sizeof(path), "/proc/%d/ns/uts", getppid());
int parent_ns = CHECK(open(path, O_RDONLY));
CHECK(setns(parent_ns, 0));
close(parent_ns);
exit(0);
}
int status;
TEST_RES(waitpid(pid, &status, 0), pid);
TEST_RES(WIFEXITED(status), 1);
}
END_TEST()
FN_TEST(proc_fd_name)
{
char path[PATH_MAX];
char buf1[256];
char buf2[256];
for (size_t i = 0; i < sizeof(ns_files) / sizeof(ns_files[0]); i++) {
snprintf(path, sizeof(path), "%s/%s", NS_DIR, ns_files[i]);
memset(buf1, 0, sizeof(buf1));
TEST_SUCC(readlink(path, buf1, sizeof(buf1)));
int nsfd = TEST_SUCC(open(path, O_RDONLY));
memset(path, 0, sizeof(path));
sprintf(path, "/proc/self/fd/%d", nsfd);
memset(buf2, 0, sizeof(buf2));
TEST_RES(readlink(path, buf2, sizeof(buf2)),
strcmp(buf1, buf2) == 0);
}
}
END_TEST()

View File

@ -45,6 +45,7 @@ mmap/mmap_shared_filebacked
mmap/mmap_readahead
mmap/mmap_vmrss
namespace/mnt_ns
namespace/proc_nsfs
namespace/setns
namespace/unshare
process/group_session