mirror of
https://github.com/checkpoint-restore/criu.git
synced 2026-07-18 09:05:10 +00:00
kerndat: check support for statmount, STATMOUNT_BY_FD
statmount() can be used to query for mount using either a unique mnt_id or a file descriptor with STATMOUNT_BY_FD. We want to use statmount() with STATMOUNT_BY_FD to add support for c/r of files on unmounted bind mounts (mounts unmounted with MNT_DETACH). Signed-off-by: Bhavik Sachdev <b.sachdev1904@gmail.com>
This commit is contained in:
parent
d7b3d870ac
commit
ebfff96a10
5 changed files with 303 additions and 0 deletions
|
|
@ -1400,6 +1400,17 @@ static int check_timer_cr_ids(void)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int check_statmount_by_fd(void)
|
||||
{
|
||||
if (!kdat.has_statmount_by_fd) {
|
||||
pr_warn("statmount syscall with STATMOUNT_BY_FD is unavailable,"
|
||||
" files on unmounted bind mounts will not be supported\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* musl doesn't have a statx wrapper... */
|
||||
struct staty {
|
||||
__u32 stx_dev_major;
|
||||
|
|
@ -1721,6 +1732,7 @@ int cr_check(void)
|
|||
ret |= check_overlayfs_maps();
|
||||
ret |= check_timer_cr_ids();
|
||||
ret |= check_pagemap_scan_guard_pages();
|
||||
ret |= check_statmount_by_fd();
|
||||
|
||||
if (kdat.lsm == LSMTYPE__APPARMOR)
|
||||
ret |= check_apparmor_stacking();
|
||||
|
|
|
|||
|
|
@ -93,6 +93,9 @@ struct kerndat_s {
|
|||
bool has_madv_guard;
|
||||
bool has_pagemap_scan_guard_pages;
|
||||
bool has_binfmt_misc_sandboxing;
|
||||
bool has_statmount;
|
||||
u64 statmount_supported_mask;
|
||||
bool has_statmount_by_fd;
|
||||
};
|
||||
|
||||
extern struct kerndat_s kdat;
|
||||
|
|
|
|||
|
|
@ -57,7 +57,149 @@ static inline long sys_openat2(int dirfd, const char *pathname, struct open_how
|
|||
return syscall(__NR_openat2, dirfd, pathname, how, size);
|
||||
}
|
||||
|
||||
/* flags and structs related to statmount */
|
||||
#ifndef STATMOUNT_SB_BASIC
|
||||
#define STATMOUNT_SB_BASIC 0x00000001U /* Want/got sb_... */
|
||||
#endif
|
||||
|
||||
#ifndef STATMOUNT_MNT_BASIC
|
||||
#define STATMOUNT_MNT_BASIC 0x00000002U /* Want/got mnt_... */
|
||||
#endif
|
||||
|
||||
#ifndef STATMOUNT_PROPAGATE_FROM
|
||||
#define STATMOUNT_PROPAGATE_FROM 0x00000004U /* Want/got propagate_from */
|
||||
#endif
|
||||
|
||||
#ifndef STATMOUNT_MNT_ROOT
|
||||
#define STATMOUNT_MNT_ROOT 0x00000008U /* Want/got mnt_root */
|
||||
#endif
|
||||
|
||||
#ifndef STATMOUNT_MNT_POINT
|
||||
#define STATMOUNT_MNT_POINT 0x00000010U /* Want/got mnt_point */
|
||||
#endif
|
||||
|
||||
#ifndef STATMOUNT_FS_TYPE
|
||||
#define STATMOUNT_FS_TYPE 0x00000020U /* Want/got fs_type */
|
||||
#endif
|
||||
|
||||
#ifndef STATMOUNT_MNT_NS_ID
|
||||
#define STATMOUNT_MNT_NS_ID 0x00000040U /* Want/got mnt_ns_id */
|
||||
#endif
|
||||
|
||||
#ifndef STATMOUNT_MNT_OPTS
|
||||
#define STATMOUNT_MNT_OPTS 0x00000080U /* Want/got mnt_opts */
|
||||
#endif
|
||||
|
||||
#ifndef STATMOUNT_FS_SUBTYPE
|
||||
#define STATMOUNT_FS_SUBTYPE 0x00000100U /* Want/got fs_subtype */
|
||||
#endif
|
||||
|
||||
#ifndef STATMOUNT_SB_SOURCE
|
||||
#define STATMOUNT_SB_SOURCE 0x00000200U /* Want/got sb_source */
|
||||
#endif
|
||||
|
||||
#ifndef STATMOUNT_OPT_ARRAY
|
||||
#define STATMOUNT_OPT_ARRAY 0x00000400U /* Want/got opt_... */
|
||||
#endif
|
||||
|
||||
#ifndef STATMOUNT_OPT_SEC_ARRAY
|
||||
#define STATMOUNT_OPT_SEC_ARRAY 0x00000800U /* Want/got opt_sec... */
|
||||
#endif
|
||||
|
||||
#ifndef STATMOUNT_SUPPORTED_MASK
|
||||
#define STATMOUNT_SUPPORTED_MASK 0x00001000U /* Want/got supported mask flags */
|
||||
#endif
|
||||
|
||||
#ifndef STATMOUNT_MNT_UIDMAP
|
||||
#define STATMOUNT_MNT_UIDMAP 0x00002000U /* Want/got uidmap... */
|
||||
#endif
|
||||
|
||||
#ifndef STATMOUNT_MNT_GIDMAP
|
||||
#define STATMOUNT_MNT_GIDMAP 0x00004000U /* Want/got gidmap... */
|
||||
#endif
|
||||
|
||||
#ifndef STATMOUNT_BY_FD
|
||||
#define STATMOUNT_BY_FD 0x0000001U /* want mountinfo for given fd */
|
||||
#endif
|
||||
|
||||
#ifndef MNT_ID_REQ_SIZE_VER1
|
||||
#define MNT_ID_REQ_SIZE_VER1 32 /* sizeof second published struct */
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Structure for passing mount ID and miscellaneous parameters to statmount(2)
|
||||
* and listmount(2).
|
||||
*
|
||||
* For statmount(2) @param represents the request mask.
|
||||
* For listmount(2) @param represents the last listed mount id (or zero).
|
||||
*/
|
||||
struct cr_mnt_id_req {
|
||||
__u32 size;
|
||||
union {
|
||||
__u32 mnt_ns_fd;
|
||||
__u32 mnt_fd;
|
||||
};
|
||||
__u64 mnt_id;
|
||||
__u64 param;
|
||||
__u64 mnt_ns_id;
|
||||
};
|
||||
|
||||
/*
|
||||
* Structure for getting mount/superblock/filesystem info with statmount(2).
|
||||
*
|
||||
* The interface is similar to statx(2): individual fields or groups can be
|
||||
* selected with the @mask argument of statmount(). Kernel will set the @mask
|
||||
* field according to the supported fields.
|
||||
*
|
||||
* If string fields are selected, then the caller needs to pass a buffer that
|
||||
* has space after the fixed part of the structure. Nul terminated strings are
|
||||
* copied there and offsets relative to @str are stored in the relevant fields.
|
||||
* If the buffer is too small, then EOVERFLOW is returned. The actually used
|
||||
* size is returned in @size.
|
||||
*/
|
||||
struct cr_statmount {
|
||||
__u32 size; /* Total size, including strings */
|
||||
__u32 mnt_opts; /* [str] Options (comma separated, escaped) */
|
||||
__u64 mask; /* What results were written */
|
||||
__u32 sb_dev_major; /* Device ID */
|
||||
__u32 sb_dev_minor;
|
||||
__u64 sb_magic; /* ..._SUPER_MAGIC */
|
||||
__u32 sb_flags; /* SB_{RDONLY,SYNCHRONOUS,DIRSYNC,LAZYTIME} */
|
||||
__u32 fs_type; /* [str] Filesystem type */
|
||||
__u64 mnt_id; /* Unique ID of mount */
|
||||
__u64 mnt_parent_id; /* Unique ID of parent (for root == mnt_id) */
|
||||
__u32 mnt_id_old; /* Reused IDs used in proc/.../mountinfo */
|
||||
__u32 mnt_parent_id_old;
|
||||
__u64 mnt_attr; /* MOUNT_ATTR_... */
|
||||
__u64 mnt_propagation; /* MS_{SHARED,SLAVE,PRIVATE,UNBINDABLE} */
|
||||
__u64 mnt_peer_group; /* ID of shared peer group */
|
||||
__u64 mnt_master; /* Mount receives propagation from this ID */
|
||||
__u64 propagate_from; /* Propagation from in current namespace */
|
||||
__u32 mnt_root; /* [str] Root of mount relative to root of fs */
|
||||
__u32 mnt_point; /* [str] Mountpoint relative to current root */
|
||||
__u64 mnt_ns_id; /* ID of the mount namespace */
|
||||
__u32 fs_subtype; /* [str] Subtype of fs_type (if any) */
|
||||
__u32 sb_source; /* [str] Source string of the mount */
|
||||
__u32 opt_num; /* Number of fs options */
|
||||
__u32 opt_array; /* [str] Array of nul terminated fs options */
|
||||
__u32 opt_sec_num; /* Number of security options */
|
||||
__u32 opt_sec_array; /* [str] Array of nul terminated security options */
|
||||
__u64 supported_mask; /* Mask flags that this kernel supports */
|
||||
__u32 mnt_uidmap_num; /* Number of uid mappings */
|
||||
__u32 mnt_uidmap; /* [str] Array of uid mappings (as seen from callers namespace) */
|
||||
__u32 mnt_gidmap_num; /* Number of gid mappings */
|
||||
__u32 mnt_gidmap; /* [str] Array of gid mappings (as seen from callers namespace) */
|
||||
__u64 __spare2[43];
|
||||
char str[]; /* Variable size part containing strings */
|
||||
};
|
||||
|
||||
static inline int sys_statmount(struct cr_mnt_id_req *req, struct cr_statmount *smbuf, size_t bufsize, unsigned long flags)
|
||||
{
|
||||
return syscall(__NR_statmount, req, smbuf, bufsize, flags);
|
||||
}
|
||||
|
||||
extern int check_mount_v2(void);
|
||||
extern struct cr_statmount *do_statmount(struct cr_mnt_id_req *req, unsigned int flags);
|
||||
|
||||
struct sharing_group {
|
||||
/* This pair identifies the group */
|
||||
|
|
|
|||
116
criu/kerndat.c
116
criu/kerndat.c
|
|
@ -1736,6 +1736,114 @@ static int kerndat_has_timer_cr_ids(void)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int kerndat_has_statmount(void)
|
||||
{
|
||||
/*
|
||||
* passing in a invalid mount, the first possible
|
||||
* valid mount is (1ULL << 31) + 1. So this call
|
||||
* should always either return EINVAL or ENOENT
|
||||
* if statmount is supported.
|
||||
*/
|
||||
struct cr_mnt_id_req req = {
|
||||
.size = MNT_ID_REQ_SIZE_VER1,
|
||||
.mnt_id = 1,
|
||||
.param = STATMOUNT_MNT_BASIC,
|
||||
};
|
||||
struct cr_statmount *statmnt = do_statmount(&req, 0);
|
||||
if (statmnt) {
|
||||
pr_err("statmount() call unexpectedly succeeded during kerndat check\n");
|
||||
free(statmnt);
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch (errno) {
|
||||
case ENOSYS:
|
||||
kdat.has_statmount = false;
|
||||
kdat.statmount_supported_mask = 0;
|
||||
pr_info("statmount() isn't supported\n");
|
||||
return 0;
|
||||
case EINVAL:
|
||||
case ENOENT:
|
||||
case EPERM:
|
||||
/*
|
||||
* Treat EPERM as potentially supported so that we can still probe
|
||||
* STATMOUNT_BY_FD, whose fd-based access checks differ from mount-ID
|
||||
* lookup. If seccomp or something else blocks statmount entirely
|
||||
* that probe fails and marks statmount() as not supported.
|
||||
*/
|
||||
kdat.has_statmount = true;
|
||||
/*
|
||||
* these are the flags that we can guarantee that statmount supports
|
||||
* as they were introduced alongside the syscall.
|
||||
* This gets updated to the actual mask supported below
|
||||
* in kerndat_has_statmount_by_fd().
|
||||
*/
|
||||
kdat.statmount_supported_mask = STATMOUNT_SB_BASIC |
|
||||
STATMOUNT_MNT_BASIC |
|
||||
STATMOUNT_PROPAGATE_FROM |
|
||||
STATMOUNT_MNT_ROOT |
|
||||
STATMOUNT_MNT_POINT |
|
||||
STATMOUNT_FS_TYPE;
|
||||
return 0;
|
||||
default:
|
||||
pr_perror("statmount() call returned unexpected errno, assuming statmount() is not supported");
|
||||
kdat.has_statmount = false;
|
||||
kdat.statmount_supported_mask = 0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int kerndat_has_statmount_by_fd(void)
|
||||
{
|
||||
struct cr_statmount *statmnt;
|
||||
int tempfd, ret = 0;
|
||||
struct cr_mnt_id_req req = {
|
||||
.size = MNT_ID_REQ_SIZE_VER1,
|
||||
.param = STATMOUNT_SUPPORTED_MASK,
|
||||
};
|
||||
char tempfile[PATH_MAX] = "/tmp/.criu.kerndat.XXXXXX";
|
||||
|
||||
if (!kdat.has_statmount) {
|
||||
kdat.has_statmount_by_fd = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
tempfd = mkstemp(tempfile);
|
||||
if (tempfd < 0) {
|
||||
pr_perror("could not create temporary file");
|
||||
return -1;
|
||||
}
|
||||
|
||||
req.mnt_fd = tempfd;
|
||||
statmnt = do_statmount(&req, STATMOUNT_BY_FD);
|
||||
if (statmnt) {
|
||||
kdat.has_statmount_by_fd = true;
|
||||
if (statmnt->mask & STATMOUNT_SUPPORTED_MASK)
|
||||
kdat.statmount_supported_mask = statmnt->supported_mask;
|
||||
free(statmnt);
|
||||
goto out;
|
||||
}
|
||||
|
||||
switch (errno) {
|
||||
case ENOMEM:
|
||||
case EFAULT:
|
||||
pr_perror("failed to check for STATMOUNT_BY_FD support");
|
||||
ret = -1;
|
||||
goto out;
|
||||
case EPERM:
|
||||
kdat.has_statmount = false;
|
||||
kdat.statmount_supported_mask = 0;
|
||||
/* fallthrough */
|
||||
default:
|
||||
pr_info("STATMOUNT_BY_FD flag isn't supported\n");
|
||||
kdat.has_statmount_by_fd = false;
|
||||
}
|
||||
out:
|
||||
close(tempfd);
|
||||
unlink(tempfile);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int kerndat_has_madv_guard(void)
|
||||
{
|
||||
void *map;
|
||||
|
|
@ -2130,6 +2238,14 @@ int kerndat_init(void)
|
|||
pr_err("kerndat_has_binfmt_misc_sandboxing has failed when initializing kerndat.\n");
|
||||
ret = -1;
|
||||
}
|
||||
if (!ret && kerndat_has_statmount()) {
|
||||
pr_err("kerndat_has_statmount failed when initializing kerndat.\n");
|
||||
ret = -1;
|
||||
}
|
||||
if (!ret && kerndat_has_statmount_by_fd()) {
|
||||
pr_err("kerndat_has_statmount_by_fd failed when initializing kerndat.\n");
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
kerndat_lsm();
|
||||
kerndat_mmap_min_addr();
|
||||
|
|
|
|||
|
|
@ -45,6 +45,36 @@ int check_mount_v2(void)
|
|||
return 0;
|
||||
}
|
||||
|
||||
struct cr_statmount *do_statmount(struct cr_mnt_id_req *req, unsigned int flags)
|
||||
{
|
||||
size_t bufsize = 1 << 15;
|
||||
struct cr_statmount *stmnt = NULL, *tmp = NULL;
|
||||
int ret;
|
||||
|
||||
for (;;) {
|
||||
tmp = xrealloc(stmnt, bufsize);
|
||||
if (!tmp)
|
||||
goto out;
|
||||
|
||||
stmnt = tmp;
|
||||
ret = sys_statmount(req, stmnt, bufsize, flags);
|
||||
if (!ret)
|
||||
return stmnt;
|
||||
|
||||
if (errno != EOVERFLOW)
|
||||
goto out;
|
||||
|
||||
bufsize <<= 1;
|
||||
if (bufsize >= UINT_MAX / 2)
|
||||
goto out;
|
||||
}
|
||||
|
||||
out:
|
||||
free(stmnt);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static struct sharing_group *get_sharing_group(int shared_id, int master_id)
|
||||
{
|
||||
struct sharing_group *sg;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue