nsresourced: detect and clean up registry entries for dead user namespaces (#42070)
The BPF kprobe that fires on user namespace destruction is the only thing that triggers registry cleanup, so any time it doesn't run — ring buffer overflow, kprobe missing, fdstore entry dropped outside our cleanup path — a registry entry is left behind forever. Stamp each registry entry with the kernel's unique namespace identifier (NS_GET_ID, kernel ≥ 6.13) at allocation time. At manager startup, after the existing fdstore→registry sweep, walk the registry and ask the kernel to look each namespace up by id via open_by_handle_at() on nsfs; if the lookup returns -ESTALE the namespace is gone and we release the entry. Old entries written before this change carry no identifier and are left alone. Add a namespace_open_by_id() helper for the lookup. The kernel restricts open_by_handle_at() on nsfs to processes in the initial user namespace, collapsing both permission denials and dead namespaces onto -ESTALE; the helper refuses early with -EPERM outside the initial user namespace so callers can tell the two apart.
This commit is contained in:
@@ -23,6 +23,7 @@
|
||||
#include "stat-util.h"
|
||||
#include "stdio-util.h"
|
||||
#include "uid-range.h"
|
||||
#include "unaligned.h"
|
||||
#include "user-util.h"
|
||||
|
||||
const struct namespace_info namespace_info[_NAMESPACE_TYPE_MAX + 1] = {
|
||||
@@ -860,6 +861,70 @@ int process_is_owned_by_uid(const PidRef *pidref, uid_t uid) {
|
||||
}
|
||||
}
|
||||
|
||||
int namespace_open_by_id(uint64_t ns_id) {
|
||||
int r;
|
||||
|
||||
/* Looks up a namespace by its unique boot-stable identifier and returns an O_PATH fd to it.
|
||||
* Requires kernel ≥ 6.13.
|
||||
*
|
||||
* Returns -ESTALE if the namespace no longer exists, or if the kernel refuses the lookup
|
||||
* for permission reasons. The latter happens outside the initial user namespace: the
|
||||
* kernel only permits open_by_handle_at() on nsfs when the caller is in the initial user
|
||||
* and pid namespaces with CAP_SYS_ADMIN, with a narrow exception for lookups of the
|
||||
* caller's own user namespace and its ancestors. To avoid conflating "namespace is dead"
|
||||
* with "kernel refused us", we refuse early with -EPERM when we aren't in the initial
|
||||
* user/pid namespace or missing CAP_SYS_ADMIN and let the caller skip the check. */
|
||||
|
||||
if (ns_id == 0)
|
||||
return -EINVAL;
|
||||
|
||||
r = namespace_is_init(NAMESPACE_USER);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return -EPERM;
|
||||
|
||||
r = namespace_is_init(NAMESPACE_PID);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return -EPERM;
|
||||
|
||||
r = have_effective_cap(CAP_SYS_ADMIN);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return -EPERM;
|
||||
|
||||
/* The natural way to write this would be a compound designated initializer:
|
||||
*
|
||||
* union { ... } fh = {
|
||||
* .file_handle.handle_bytes = sizeof(struct nsfs_file_handle),
|
||||
* .file_handle.handle_type = FILEID_NSFS,
|
||||
* };
|
||||
*
|
||||
* but that only zero-initializes the named struct members of struct file_handle.
|
||||
* struct file_handle ends with a flexible array (`unsigned char f_handle[]`), whose
|
||||
* storage comes from the overlapping `space[]` member of the union. Bytes in that storage
|
||||
* are not covered by the partial struct initializer and end up as stack garbage. Zero the
|
||||
* entire union first, then fill in the fields explicitly. */
|
||||
|
||||
union {
|
||||
struct file_handle file_handle;
|
||||
uint8_t space[offsetof(struct file_handle, f_handle) + sizeof(struct nsfs_file_handle)];
|
||||
} fh = {};
|
||||
fh.file_handle.handle_bytes = sizeof(struct nsfs_file_handle);
|
||||
fh.file_handle.handle_type = FILEID_NSFS;
|
||||
|
||||
/* The first 8 bytes of struct nsfs_file_handle (see <linux/nsfs.h>, uapi since kernel v6.18)
|
||||
* are __u64 ns_id; the remaining ns_type/ns_inum fields stay zero so the kernel looks up by
|
||||
* id alone. The kernel made lookup-by-id-only an explicit ABI guarantee in v6.19 via commit
|
||||
* 04173501a69e ("nstree: allow lookup solely based on inode"). */
|
||||
unaligned_write_ne64(fh.file_handle.f_handle, ns_id);
|
||||
|
||||
return RET_NERRNO(open_by_handle_at(FD_NSFS_ROOT, &fh.file_handle, O_PATH|O_CLOEXEC));
|
||||
}
|
||||
|
||||
int is_idmapping_supported(const char *path) {
|
||||
_cleanup_close_ int mount_fd = -EBADF, userns_fd = -EBADF, dir_fd = -EBADF;
|
||||
int r;
|
||||
|
||||
@@ -88,6 +88,8 @@ bool userns_supported(void);
|
||||
|
||||
int userns_get_base_uid(int userns_fd, uid_t *ret_uid, gid_t *ret_gid);
|
||||
|
||||
int namespace_open_by_id(uint64_t ns_id);
|
||||
|
||||
int process_is_owned_by_uid(const PidRef *pidref, uid_t uid);
|
||||
|
||||
int is_idmapping_supported(const char *path);
|
||||
|
||||
@@ -25,6 +25,10 @@
|
||||
#define AT_HANDLE_MNT_ID_UNIQUE 0x001 /* Return the u64 unique mount ID. */
|
||||
#endif
|
||||
|
||||
#ifndef FD_NSFS_ROOT
|
||||
#define FD_NSFS_ROOT -10003 /* Root of the nsfs filesystem */
|
||||
#endif
|
||||
|
||||
/* Defined since glibc-2.42.
|
||||
* Supported since kernel v5.6 (fddb5d430ad9fa91b49b1d34d0202ffe2fa0e179). */
|
||||
int openat2_shim(int dfd, const char *filename, const struct open_how *how, size_t usize);
|
||||
|
||||
@@ -12,3 +12,8 @@
|
||||
#define PROC_PID_INIT_INO ((ino_t) UINT32_C(0xEFFFFFFC))
|
||||
#define PROC_CGROUP_INIT_INO ((ino_t) UINT32_C(0xEFFFFFFB))
|
||||
#define PROC_TIME_INIT_INO ((ino_t) UINT32_C(0xEFFFFFFA))
|
||||
|
||||
/* From kernel-internal include/linux/exportfs.h, not part of uapi. */
|
||||
#ifndef FILEID_NSFS
|
||||
#define FILEID_NSFS 0xf1
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
|
||||
#ifndef __LINUX_NSFS_H
|
||||
#define __LINUX_NSFS_H
|
||||
|
||||
#include <linux/ioctl.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#define NSIO 0xb7
|
||||
|
||||
/* Returns a file descriptor that refers to an owning user namespace */
|
||||
#define NS_GET_USERNS _IO(NSIO, 0x1)
|
||||
/* Returns a file descriptor that refers to a parent namespace */
|
||||
#define NS_GET_PARENT _IO(NSIO, 0x2)
|
||||
/* Returns the type of namespace (CLONE_NEW* value) referred to by
|
||||
file descriptor */
|
||||
#define NS_GET_NSTYPE _IO(NSIO, 0x3)
|
||||
/* Get owner UID (in the caller's user namespace) for a user namespace */
|
||||
#define NS_GET_OWNER_UID _IO(NSIO, 0x4)
|
||||
/* Translate pid from target pid namespace into the caller's pid namespace. */
|
||||
#define NS_GET_PID_FROM_PIDNS _IOR(NSIO, 0x6, int)
|
||||
/* Return thread-group leader id of pid in the callers pid namespace. */
|
||||
#define NS_GET_TGID_FROM_PIDNS _IOR(NSIO, 0x7, int)
|
||||
/* Translate pid from caller's pid namespace into a target pid namespace. */
|
||||
#define NS_GET_PID_IN_PIDNS _IOR(NSIO, 0x8, int)
|
||||
/* Return thread-group leader id of pid in the target pid namespace. */
|
||||
#define NS_GET_TGID_IN_PIDNS _IOR(NSIO, 0x9, int)
|
||||
|
||||
struct mnt_ns_info {
|
||||
__u32 size;
|
||||
__u32 nr_mounts;
|
||||
__u64 mnt_ns_id;
|
||||
};
|
||||
|
||||
#define MNT_NS_INFO_SIZE_VER0 16 /* size of first published struct */
|
||||
|
||||
/* Get information about namespace. */
|
||||
#define NS_MNT_GET_INFO _IOR(NSIO, 10, struct mnt_ns_info)
|
||||
/* Get next namespace. */
|
||||
#define NS_MNT_GET_NEXT _IOR(NSIO, 11, struct mnt_ns_info)
|
||||
/* Get previous namespace. */
|
||||
#define NS_MNT_GET_PREV _IOR(NSIO, 12, struct mnt_ns_info)
|
||||
|
||||
/* Retrieve namespace identifiers. */
|
||||
#define NS_GET_MNTNS_ID _IOR(NSIO, 5, __u64)
|
||||
#define NS_GET_ID _IOR(NSIO, 13, __u64)
|
||||
|
||||
enum init_ns_ino {
|
||||
IPC_NS_INIT_INO = 0xEFFFFFFFU,
|
||||
UTS_NS_INIT_INO = 0xEFFFFFFEU,
|
||||
USER_NS_INIT_INO = 0xEFFFFFFDU,
|
||||
PID_NS_INIT_INO = 0xEFFFFFFCU,
|
||||
CGROUP_NS_INIT_INO = 0xEFFFFFFBU,
|
||||
TIME_NS_INIT_INO = 0xEFFFFFFAU,
|
||||
NET_NS_INIT_INO = 0xEFFFFFF9U,
|
||||
MNT_NS_INIT_INO = 0xEFFFFFF8U,
|
||||
#ifdef __KERNEL__
|
||||
MNT_NS_ANON_INO = 0xEFFFFFF7U,
|
||||
#endif
|
||||
};
|
||||
|
||||
struct nsfs_file_handle {
|
||||
__u64 ns_id;
|
||||
__u32 ns_type;
|
||||
__u32 ns_inum;
|
||||
};
|
||||
|
||||
#define NSFS_FILE_HANDLE_SIZE_VER0 16 /* sizeof first published struct */
|
||||
#define NSFS_FILE_HANDLE_SIZE_LATEST sizeof(struct nsfs_file_handle) /* sizeof latest published struct */
|
||||
|
||||
enum init_ns_id {
|
||||
IPC_NS_INIT_ID = 1ULL,
|
||||
UTS_NS_INIT_ID = 2ULL,
|
||||
USER_NS_INIT_ID = 3ULL,
|
||||
PID_NS_INIT_ID = 4ULL,
|
||||
CGROUP_NS_INIT_ID = 5ULL,
|
||||
TIME_NS_INIT_ID = 6ULL,
|
||||
NET_NS_INIT_ID = 7ULL,
|
||||
MNT_NS_INIT_ID = 8ULL,
|
||||
#ifdef __KERNEL__
|
||||
NS_LAST_INIT_ID = MNT_NS_INIT_ID,
|
||||
#endif
|
||||
};
|
||||
|
||||
enum ns_type {
|
||||
TIME_NS = (1ULL << 7), /* CLONE_NEWTIME */
|
||||
MNT_NS = (1ULL << 17), /* CLONE_NEWNS */
|
||||
CGROUP_NS = (1ULL << 25), /* CLONE_NEWCGROUP */
|
||||
UTS_NS = (1ULL << 26), /* CLONE_NEWUTS */
|
||||
IPC_NS = (1ULL << 27), /* CLONE_NEWIPC */
|
||||
USER_NS = (1ULL << 28), /* CLONE_NEWUSER */
|
||||
PID_NS = (1ULL << 29), /* CLONE_NEWPID */
|
||||
NET_NS = (1ULL << 30), /* CLONE_NEWNET */
|
||||
};
|
||||
|
||||
/**
|
||||
* struct ns_id_req - namespace ID request structure
|
||||
* @size: size of this structure
|
||||
* @spare: reserved for future use
|
||||
* @filter: filter mask
|
||||
* @ns_id: last namespace id
|
||||
* @user_ns_id: owning user namespace ID
|
||||
*
|
||||
* Structure for passing namespace ID and miscellaneous parameters to
|
||||
* statns(2) and listns(2).
|
||||
*
|
||||
* For statns(2) @param represents the request mask.
|
||||
* For listns(2) @param represents the last listed mount id (or zero).
|
||||
*/
|
||||
struct ns_id_req {
|
||||
__u32 size;
|
||||
__u32 spare;
|
||||
__u64 ns_id;
|
||||
struct /* listns */ {
|
||||
__u32 ns_type;
|
||||
__u32 spare2;
|
||||
__u64 user_ns_id;
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
* Special @user_ns_id value that can be passed to listns()
|
||||
*/
|
||||
#define LISTNS_CURRENT_USER 0xffffffffffffffff /* Caller's userns */
|
||||
|
||||
/* List of all ns_id_req versions. */
|
||||
#define NS_ID_REQ_SIZE_VER0 32 /* sizeof first published struct */
|
||||
|
||||
#endif /* __LINUX_NSFS_H */
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "fs-util.h"
|
||||
#include "log.h"
|
||||
#include "mkdir.h"
|
||||
#include "namespace-util.h"
|
||||
#include "nsresourced-manager.h"
|
||||
#include "parse-util.h"
|
||||
#include "pidfd-util.h"
|
||||
@@ -648,6 +649,51 @@ int manager_startup(Manager *m) {
|
||||
manager_release_userns_by_inode(m, inode);
|
||||
}
|
||||
|
||||
/* Look for registry entries whose user namespace has died without us getting a BPF
|
||||
* notification — e.g. because the BPF ring buffer overflowed, the kprobe is missing, or
|
||||
* something else dropped the fd store entry without going through our cleanup path. Each
|
||||
* registry entry stores the kernel's unique namespace identifier; ask the kernel to open
|
||||
* the namespace by that identifier and release the entry if the lookup fails. Entries
|
||||
* written by older versions don't carry the identifier, and old kernels (or running
|
||||
* outside the initial user namespace) don't support lookup by it — in those cases we leave
|
||||
* the entry alone. */
|
||||
|
||||
SET_FOREACH(p, registry_inodes) {
|
||||
uint64_t inode = PTR_TO_UINT32(p);
|
||||
|
||||
_cleanup_(userns_info_freep) UserNamespaceInfo *userns_info = NULL;
|
||||
r = userns_registry_load_by_userns_inode(m->registry_fd, inode, &userns_info);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to load registry entry for user namespace %" PRIu64 ", ignoring: %m", inode);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (userns_info->userns_id == 0)
|
||||
continue; /* Entry predates ns_id tracking, can't probe authoritatively */
|
||||
|
||||
_cleanup_close_ int probe_fd = namespace_open_by_id(userns_info->userns_id);
|
||||
if (probe_fd >= 0)
|
||||
continue; /* User namespace is still alive */
|
||||
/* EPERM/EACCES means we're not in the initial user/pid namespace or missing
|
||||
* CAP_SYS_ADMIN; ENOTSUP/ENOSYS means the kernel is too old for
|
||||
* open_by_handle_at() on nsfs. Either way the sweep can't proceed for any
|
||||
* entry, so bail out rather than logging once per entry. */
|
||||
if (ERRNO_IS_NEG_PRIVILEGE(probe_fd) || ERRNO_IS_NEG_NOT_SUPPORTED(probe_fd)) {
|
||||
log_debug_errno(probe_fd, "Cannot detect stale registry entries, skipping: %m");
|
||||
break;
|
||||
}
|
||||
/* Anything else except ESTALE is unexpected — log it but skip just this one. */
|
||||
if (probe_fd != -ESTALE) {
|
||||
log_debug_errno(probe_fd, "Failed to probe liveness of user namespace %" PRIu64 " (id %" PRIu64 "), ignoring: %m",
|
||||
inode, userns_info->userns_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
log_debug("Registry entry for user namespace %" PRIu64 " (id %" PRIu64 ") refers to a dead namespace, removing.",
|
||||
inode, userns_info->userns_id);
|
||||
manager_release_userns_by_inode(m, inode);
|
||||
}
|
||||
|
||||
r = manager_make_listen_socket(m);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -1295,6 +1295,8 @@ static int vl_method_allocate_user_range(sd_varlink *link, sd_json_variant *para
|
||||
|
||||
userns_info->owner = peer_uid;
|
||||
userns_info->userns_inode = userns_st.st_ino;
|
||||
if (ioctl(userns_fd, NS_GET_ID, &userns_info->userns_id) < 0)
|
||||
log_debug_errno(errno, "Failed to query userns ID, ignoring: %m");
|
||||
userns_info->size = p.size;
|
||||
userns_info->target_uid = p.target;
|
||||
userns_info->target_gid = (gid_t) p.target;
|
||||
@@ -1575,6 +1577,8 @@ static int vl_method_register_user_namespace(sd_varlink *link, sd_json_variant *
|
||||
|
||||
userns_info->owner = peer_uid;
|
||||
userns_info->userns_inode = userns_st.st_ino;
|
||||
if (ioctl(userns_fd, NS_GET_ID, &userns_info->userns_id) < 0)
|
||||
log_debug_errno(errno, "Failed to query userns ID, ignoring: %m");
|
||||
|
||||
r = userns_registry_store(registry_dir_fd, userns_info);
|
||||
if (r < 0)
|
||||
|
||||
@@ -239,6 +239,7 @@ static int userns_registry_load(int dir_fd, const char *fn, UserNamespaceInfo **
|
||||
{ "owner", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(UserNamespaceInfo, owner), SD_JSON_MANDATORY },
|
||||
{ "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(UserNamespaceInfo, name), SD_JSON_MANDATORY },
|
||||
{ "userns", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint64, offsetof(UserNamespaceInfo, userns_inode), SD_JSON_MANDATORY },
|
||||
{ "usernsId", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint64, offsetof(UserNamespaceInfo, userns_id), 0 },
|
||||
{ "size", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint32, offsetof(UserNamespaceInfo, size), 0 },
|
||||
{ "start", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(UserNamespaceInfo, start_uid), 0 },
|
||||
{ "target", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(UserNamespaceInfo, target_uid), 0 },
|
||||
@@ -565,6 +566,7 @@ int userns_registry_store(int dir_fd, UserNamespaceInfo *info) {
|
||||
SD_JSON_BUILD_PAIR_UNSIGNED("owner", info->owner),
|
||||
SD_JSON_BUILD_PAIR_STRING("name", info->name),
|
||||
SD_JSON_BUILD_PAIR_UNSIGNED("userns", info->userns_inode),
|
||||
SD_JSON_BUILD_PAIR_CONDITION(info->userns_id != 0, "usernsId", SD_JSON_BUILD_UNSIGNED(info->userns_id)),
|
||||
SD_JSON_BUILD_PAIR_CONDITION(info->size > 0, "size", SD_JSON_BUILD_UNSIGNED(info->size)),
|
||||
SD_JSON_BUILD_PAIR_CONDITION(uid_is_valid(info->start_uid), "start", SD_JSON_BUILD_UNSIGNED(info->start_uid)),
|
||||
SD_JSON_BUILD_PAIR_CONDITION(uid_is_valid(info->target_uid), "target", SD_JSON_BUILD_UNSIGNED(info->target_uid)),
|
||||
|
||||
@@ -29,6 +29,7 @@ typedef struct UserNamespaceInfo {
|
||||
uid_t owner;
|
||||
char *name;
|
||||
uint64_t userns_inode;
|
||||
uint64_t userns_id; /* Unique namespace identifier from NS_GET_ID, 0 if unavailable */
|
||||
uint32_t size;
|
||||
uid_t start_uid;
|
||||
uid_t target_uid;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <linux/nsfs.h>
|
||||
#include <sched.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
@@ -282,6 +284,57 @@ TEST(userns_get_base_uid) {
|
||||
ASSERT_ERROR(userns_get_base_uid(fd, &base_uid, &base_gid), ENOMSG);
|
||||
}
|
||||
|
||||
TEST(namespace_open_by_id) {
|
||||
/* Try our own user namespace first to see if the kernel exposes ns_id at all. */
|
||||
_cleanup_close_ int userns_fd = ASSERT_OK_ERRNO(open("/proc/self/ns/user", O_RDONLY|O_CLOEXEC));
|
||||
|
||||
uint64_t ns_id;
|
||||
int r = RET_NERRNO(ioctl(userns_fd, NS_GET_ID, &ns_id));
|
||||
if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
|
||||
return (void) log_tests_skipped("NS_GET_ID is not supported by this kernel");
|
||||
ASSERT_OK(r);
|
||||
|
||||
/* namespace_open_by_id() refuses with -EPERM outside the initial user/pid namespace, since
|
||||
* the kernel restricts open_by_handle_at() on nsfs to the initial userns and pidns and to
|
||||
* CAP_SYS_ADMIN. */
|
||||
_cleanup_close_ int opened = namespace_open_by_id(ns_id);
|
||||
if (opened == -EPERM)
|
||||
return (void) log_tests_skipped("not in initial user namespace or missing CAP_SYS_ADMIN");
|
||||
if (IN_SET(opened, -EOPNOTSUPP, -EINVAL))
|
||||
return (void) log_tests_skipped("nsfs lookup by ns_id is not supported by this kernel");
|
||||
ASSERT_OK(opened);
|
||||
|
||||
struct stat orig_st, opened_st;
|
||||
ASSERT_OK_ERRNO(fstat(userns_fd, &orig_st));
|
||||
ASSERT_OK_ERRNO(fstat(opened, &opened_st));
|
||||
ASSERT_EQ(orig_st.st_ino, opened_st.st_ino);
|
||||
|
||||
opened = safe_close(opened);
|
||||
|
||||
ASSERT_ERROR(namespace_open_by_id(0), EINVAL);
|
||||
|
||||
_cleanup_close_ int transient_fd = userns_acquire_empty();
|
||||
if (ERRNO_IS_NEG_NOT_SUPPORTED(transient_fd) || ERRNO_IS_NEG_PRIVILEGE(transient_fd))
|
||||
return (void) log_tests_skipped("cannot acquire userns for transient lookup test");
|
||||
ASSERT_OK(transient_fd);
|
||||
|
||||
uint64_t transient_id;
|
||||
ASSERT_OK_ERRNO(ioctl(transient_fd, NS_GET_ID, &transient_id));
|
||||
ASSERT_NE(transient_id, ns_id);
|
||||
|
||||
opened = ASSERT_OK(namespace_open_by_id(transient_id));
|
||||
|
||||
struct stat transient_st, transient_opened_st;
|
||||
ASSERT_OK_ERRNO(fstat(transient_fd, &transient_st));
|
||||
ASSERT_OK_ERRNO(fstat(opened, &transient_opened_st));
|
||||
ASSERT_EQ(transient_st.st_ino, transient_opened_st.st_ino);
|
||||
opened = safe_close(opened);
|
||||
|
||||
/* Close the only reference. The namespace is now dead — lookup must fail. */
|
||||
transient_fd = safe_close(transient_fd);
|
||||
ASSERT_ERROR(namespace_open_by_id(transient_id), ESTALE);
|
||||
}
|
||||
|
||||
TEST(process_is_owned_by_uid) {
|
||||
int r;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user