Support idmapped mount

Adds support for idmapped mounts.  Supported as of Linux 5.12 this 
functionality allows user and group IDs to be remapped without changing 
their state on disk.  This can be useful for portable home directories
and a variety of container related use cases.

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Ryan Moeller <ryan@iXsystems.com>
Signed-off-by: Youzhong Yang <yyang@mathworks.com>
Closes #12923
Closes #13671
This commit is contained in:
youzhongyang
2022-10-19 14:17:09 -04:00
committed by GitHub
parent eaaed26ffb
commit 2a068a1394
41 changed files with 1636 additions and 166 deletions
+1
View File
@@ -47,3 +47,4 @@
/edonr_test
/skein_test
/sha2_test
/idmap_util
+2
View File
@@ -118,7 +118,9 @@ scripts_zfs_tests_bin_PROGRAMS += %D%/getversion
scripts_zfs_tests_bin_PROGRAMS += %D%/user_ns_exec
scripts_zfs_tests_bin_PROGRAMS += %D%/xattrtest
scripts_zfs_tests_bin_PROGRAMS += %D%/zed_fd_spill-zedlet
scripts_zfs_tests_bin_PROGRAMS += %D%/idmap_util
%C%_idmap_util_LDADD = libspl.la
dist_noinst_DATA += %D%/linux_dos_attributes/dos_attributes.h
scripts_zfs_tests_bin_PROGRAMS += %D%/read_dos_attributes %D%/write_dos_attributes
+791
View File
@@ -0,0 +1,791 @@
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or https://opensource.org/licenses/CDDL-1.0.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#include <linux/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <fcntl.h>
#include <errno.h>
#include <sched.h>
#include <syscall.h>
#include <sys/list.h>
#ifndef UINT_MAX
#define UINT_MAX 4294967295U
#endif
#ifndef __NR_Linux
#if defined __alpha__
#define __NR_Linux 110
#elif defined _MIPS_SIM
#if _MIPS_SIM == _MIPS_SIM_ABI32
#define __NR_Linux 4000
#endif
#if _MIPS_SIM == _MIPS_SIM_NABI32
#define __NR_Linux 6000
#endif
#if _MIPS_SIM == _MIPS_SIM_ABI64
#define __NR_Linux 5000
#endif
#elif defined __ia64__
#define __NR_Linux 1024
#else
#define __NR_Linux 0
#endif
#endif
#ifndef __NR_mount_setattr
#define __NR_mount_setattr (442 + __NR_Linux)
#endif
#ifndef __NR_open_tree
#define __NR_open_tree (428 + __NR_Linux)
#endif
#ifndef __NR_move_mount
#define __NR_move_mount (429 + __NR_Linux)
#endif
#ifndef MNT_DETACH
#define MNT_DETACH 2
#endif
#ifndef MOVE_MOUNT_F_EMPTY_PATH
#define MOVE_MOUNT_F_EMPTY_PATH 0x00000004
#endif
#ifndef MOUNT_ATTR_IDMAP
#define MOUNT_ATTR_IDMAP 0x00100000
#endif
#ifndef OPEN_TREE_CLONE
#define OPEN_TREE_CLONE 1
#endif
#ifndef OPEN_TREE_CLOEXEC
#define OPEN_TREE_CLOEXEC O_CLOEXEC
#endif
#ifndef AT_RECURSIVE
#define AT_RECURSIVE 0x8000
#endif
#ifndef mount_attr
struct mount_attr {
__u64 attr_set;
__u64 attr_clr;
__u64 propagation;
__u64 userns_fd;
};
#endif
static inline int
sys_mount_setattr(int dfd, const char *path, unsigned int flags,
struct mount_attr *attr, size_t size)
{
return (syscall(__NR_mount_setattr, dfd, path, flags, attr, size));
}
static inline int
sys_open_tree(int dfd, const char *filename, unsigned int flags)
{
return (syscall(__NR_open_tree, dfd, filename, flags));
}
static inline int sys_move_mount(int from_dfd, const char *from_pathname,
int to_dfd, const char *to_pathname, unsigned int flags)
{
return (syscall(__NR_move_mount, from_dfd, from_pathname, to_dfd,
to_pathname, flags));
}
typedef enum idmap_type_t {
TYPE_UID,
TYPE_GID,
TYPE_BOTH
} idmap_type_t;
struct idmap_entry {
__u32 first;
__u32 lower_first;
__u32 count;
idmap_type_t type;
list_node_t node;
};
static void
log_msg(const char *msg, ...)
{
va_list ap;
va_start(ap, msg);
vfprintf(stderr, msg, ap);
fputc('\n', stderr);
va_end(ap);
}
#define log_errno(msg, args...) \
do { \
log_msg("%s:%d:%s: [%m] " msg, __FILE__, __LINE__,\
__FUNCTION__, ##args); \
} while (0)
/*
* Parse the idmapping in the following format
* and add to the list:
*
* u:nsid_first:hostid_first:count
* g:nsid_first:hostid_first:count
* b:nsid_first:hostid_first:count
*
* The delimiter can be : or space character.
*
* Return:
* 0 if success
* ENOMEM if out of memory
* EINVAL if wrong arg or input
*/
static int
parse_idmap_entry(list_t *head, char *input)
{
char *token, *savedptr = NULL;
struct idmap_entry *entry;
unsigned long ul;
char *delimiter = (char *)": ";
char c;
if (!input || !head)
return (EINVAL);
entry = malloc(sizeof (*entry));
if (!entry)
return (ENOMEM);
token = strtok_r(input, delimiter, &savedptr);
if (token)
c = token[0];
if (!token || (c != 'b' && c != 'u' && c != 'g'))
goto errout;
entry->type = (c == 'b') ? TYPE_BOTH :
((c == 'u') ? TYPE_UID : TYPE_GID);
token = strtok_r(NULL, delimiter, &savedptr);
if (!token)
goto errout;
ul = strtoul(token, NULL, 10);
if (ul > UINT_MAX || errno != 0)
goto errout;
entry->first = (__u32)ul;
token = strtok_r(NULL, delimiter, &savedptr);
if (!token)
goto errout;
ul = strtoul(token, NULL, 10);
if (ul > UINT_MAX || errno != 0)
goto errout;
entry->lower_first = (__u32)ul;
token = strtok_r(NULL, delimiter, &savedptr);
if (!token)
goto errout;
ul = strtoul(token, NULL, 10);
if (ul > UINT_MAX || errno != 0)
goto errout;
entry->count = (__u32)ul;
list_insert_tail(head, entry);
return (0);
errout:
free(entry);
return (EINVAL);
}
/*
* Release all the entries in the list
*/
static void
free_idmap(list_t *head)
{
struct idmap_entry *entry;
while ((entry = list_remove_head(head)) != NULL)
free(entry);
/* list_destroy() to be done by the caller */
}
/*
* Write all bytes in the buffer to fd
*/
static ssize_t
write_buf(int fd, const char *buf, size_t buf_size)
{
ssize_t written, total_written = 0;
size_t remaining = buf_size;
char *position = (char *)buf;
for (;;) {
written = write(fd, position, remaining);
if (written < 0 && errno == EINTR)
continue;
if (written < 0) {
log_errno("write");
return (written);
}
total_written += written;
if (total_written == buf_size)
break;
remaining -= written;
position += written;
}
return (total_written);
}
/*
* Read data from file into buffer
*/
static ssize_t
read_buf(int fd, char *buf, size_t buf_size)
{
int ret;
for (;;) {
ret = read(fd, buf, buf_size);
if (ret < 0 && errno == EINTR)
continue;
break;
}
if (ret < 0)
log_errno("read");
return (ret);
}
/*
* Write idmap of the given type in the buffer to the
* process' uid_map or gid_map proc file.
*
* Return:
* 0 if success
* errno if there's any error
*/
static int
write_idmap(pid_t pid, char *buf, size_t buf_size, idmap_type_t type)
{
char path[PATH_MAX];
int fd = -EBADF;
int ret;
(void) snprintf(path, sizeof (path), "/proc/%d/%cid_map",
pid, type == TYPE_UID ? 'u' : 'g');
fd = open(path, O_WRONLY | O_CLOEXEC);
if (fd < 0) {
ret = errno;
log_errno("open(%s)", path);
goto out;
}
ret = write_buf(fd, buf, buf_size);
if (ret < 0)
ret = errno;
else
ret = 0;
out:
if (fd > 0)
close(fd);
return (ret);
}
/*
* Write idmap info in the list to the process
* user namespace, i.e. its /proc/<pid>/uid_map
* and /proc/<pid>/gid_map file.
*
* Return:
* 0 if success
* errno if it fails
*/
static int
write_pid_idmaps(pid_t pid, list_t *head)
{
char *buf_uids, *buf_gids;
char *curr_bufu, *curr_bufg;
/* max 4k to be allowed for each map */
int size_buf_uids = 4096, size_buf_gids = 4096;
struct idmap_entry *entry;
int uid_filled, gid_filled;
int ret;
int has_uids = 0, has_gids = 0;
size_t buf_size;
buf_uids = malloc(size_buf_uids);
if (!buf_uids)
return (ENOMEM);
buf_gids = malloc(size_buf_gids);
if (!buf_gids) {
free(buf_uids);
return (ENOMEM);
}
curr_bufu = buf_uids;
curr_bufg = buf_gids;
for (entry = list_head(head); entry; entry = list_next(head, entry)) {
if (entry->type == TYPE_UID || entry->type == TYPE_BOTH) {
uid_filled = snprintf(curr_bufu, size_buf_uids,
"%u %u %u\n", entry->first, entry->lower_first,
entry->count);
if (uid_filled <= 0 || uid_filled >= size_buf_uids) {
ret = E2BIG;
goto out;
}
curr_bufu += uid_filled;
size_buf_uids -= uid_filled;
has_uids = 1;
}
if (entry->type == TYPE_GID || entry->type == TYPE_BOTH) {
gid_filled = snprintf(curr_bufg, size_buf_gids,
"%u %u %u\n", entry->first, entry->lower_first,
entry->count);
if (gid_filled <= 0 || gid_filled >= size_buf_gids) {
ret = E2BIG;
goto out;
}
curr_bufg += gid_filled;
size_buf_gids -= gid_filled;
has_gids = 1;
}
}
if (has_uids) {
buf_size = curr_bufu - buf_uids;
ret = write_idmap(pid, buf_uids, buf_size, TYPE_UID);
if (ret)
goto out;
}
if (has_gids) {
buf_size = curr_bufg - buf_gids;
ret = write_idmap(pid, buf_gids, buf_size, TYPE_GID);
}
out:
free(buf_uids);
free(buf_gids);
return (ret);
}
/*
* Wait for the child process to exit
* and reap it.
*
* Return:
* process exit code if available
*/
static int
wait_for_pid(pid_t pid)
{
int status;
int ret;
for (;;) {
ret = waitpid(pid, &status, 0);
if (ret < 0) {
if (errno == EINTR)
continue;
return (EXIT_FAILURE);
}
break;
}
if (!WIFEXITED(status))
return (EXIT_FAILURE);
return (WEXITSTATUS(status));
}
/*
* Get the file descriptor of the process user namespace
* given its pid.
*
* Return:
* fd if success
* -1 if it fails
*/
static int
userns_fd_from_pid(pid_t pid)
{
int fd;
char path[PATH_MAX];
(void) snprintf(path, sizeof (path), "/proc/%d/ns/user", pid);
fd = open(path, O_RDONLY | O_CLOEXEC);
if (fd < 0)
log_errno("open(%s)", path);
return (fd);
}
/*
* Get the user namespace file descriptor given a list
* of idmap info.
*
* Return:
* fd if success
* -errno if it fails
*/
static int
userns_fd_from_idmap(list_t *head)
{
pid_t pid;
int ret, fd;
int pipe_fd[2];
char c;
int saved_errno = 0;
/* pipe for bidirectional communication */
ret = pipe(pipe_fd);
if (ret) {
log_errno("pipe");
return (-errno);
}
pid = fork();
if (pid < 0) {
log_errno("fork");
return (-errno);
}
if (pid == 0) {
/* child process */
close(pipe_fd[0]);
ret = unshare(CLONE_NEWUSER);
if (ret == 0) {
/* notify the parent of success */
ret = write_buf(pipe_fd[1], "1", 1);
} else {
saved_errno = errno;
log_errno("unshare");
ret = write_buf(pipe_fd[1], "0", 1);
}
if (ret < 0)
saved_errno = errno;
close(pipe_fd[1]);
exit(saved_errno);
}
/* parent process */
close(pipe_fd[1]);
ret = read_buf(pipe_fd[0], &c, 1);
if (ret == 1 && c == '1') {
ret = write_pid_idmaps(pid, head);
if (!ret) {
fd = userns_fd_from_pid(pid);
if (fd < 0)
fd = -errno;
} else {
fd = -ret;
}
} else {
fd = -EBADF;
}
close(pipe_fd[0]);
(void) wait_for_pid(pid);
return (fd);
}
/*
* Check if the operating system supports idmapped mount on the
* given path or not.
*
* Return:
* true if supported
* false if not supported
*/
static bool
is_idmap_supported(char *path)
{
list_t head;
int ret;
int tree_fd = -EBADF, path_fd = -EBADF;
struct mount_attr attr = {
.attr_set = MOUNT_ATTR_IDMAP,
.userns_fd = -EBADF,
};
/* strtok_r() won't be happy with a const string */
char *input = strdup("b:0:1000000:1000000");
if (!input) {
errno = ENOMEM;
log_errno("strdup");
return (false);
}
list_create(&head, sizeof (struct idmap_entry),
offsetof(struct idmap_entry, node));
ret = parse_idmap_entry(&head, input);
if (ret) {
errno = ret;
log_errno("parse_idmap_entry(%s)", input);
goto out;
}
ret = userns_fd_from_idmap(&head);
if (ret < 0)
goto out;
attr.userns_fd = ret;
ret = openat(-EBADF, path, O_DIRECTORY | O_CLOEXEC);
if (ret < 0) {
log_errno("openat(%s)", path);
goto out;
}
path_fd = ret;
ret = sys_open_tree(path_fd, "", AT_EMPTY_PATH | AT_NO_AUTOMOUNT |
AT_SYMLINK_NOFOLLOW | OPEN_TREE_CLOEXEC | OPEN_TREE_CLONE);
if (ret < 0) {
log_errno("sys_open_tree");
goto out;
}
tree_fd = ret;
ret = sys_mount_setattr(tree_fd, "", AT_EMPTY_PATH, &attr,
sizeof (attr));
if (ret < 0) {
log_errno("sys_mount_setattr");
}
out:
free_idmap(&head);
list_destroy(&head);
if (tree_fd > 0)
close(tree_fd);
if (path_fd > 0)
close(path_fd);
if (attr.userns_fd > 0)
close(attr.userns_fd);
free(input);
return (ret == 0);
}
/*
* Check if the given path is a mount point or not.
*
* Return:
* true if it is
* false otherwise
*/
static bool
is_mountpoint(char *path)
{
char *parent;
struct stat st_me, st_parent;
bool ret;
parent = malloc(strlen(path)+4);
if (!parent) {
errno = ENOMEM;
log_errno("malloc");
return (false);
}
strcat(strcpy(parent, path), "/..");
if (lstat(path, &st_me) != 0 ||
lstat(parent, &st_parent) != 0)
ret = false;
else
if (st_me.st_dev != st_parent.st_dev ||
st_me.st_ino == st_parent.st_ino)
ret = true;
else
ret = false;
free(parent);
return (ret);
}
/*
* Remount the source on the new target folder with the given
* list of idmap info. If target is NULL, the source will be
* unmounted and then remounted if it is a mountpoint, otherwise
* no unmount is done, the source is simply idmap remounted.
*
* Return:
* 0 if success
* -errno otherwise
*/
static int
do_idmap_mount(list_t *idmap, char *source, char *target, int flags)
{
int ret;
int tree_fd = -EBADF, source_fd = -EBADF;
struct mount_attr attr = {
.attr_set = MOUNT_ATTR_IDMAP,
.userns_fd = -EBADF,
};
ret = userns_fd_from_idmap(idmap);
if (ret < 0)
goto out;
attr.userns_fd = ret;
ret = openat(-EBADF, source, O_DIRECTORY | O_CLOEXEC);
if (ret < 0) {
ret = -errno;
log_errno("openat(%s)", source);
goto out;
}
source_fd = ret;
ret = sys_open_tree(source_fd, "", AT_EMPTY_PATH | AT_NO_AUTOMOUNT |
AT_SYMLINK_NOFOLLOW | OPEN_TREE_CLOEXEC | OPEN_TREE_CLONE | flags);
if (ret < 0) {
ret = -errno;
log_errno("sys_open_tree");
goto out;
}
tree_fd = ret;
ret = sys_mount_setattr(tree_fd, "", AT_EMPTY_PATH | flags, &attr,
sizeof (attr));
if (ret < 0) {
ret = -errno;
log_errno("sys_mount_setattr");
goto out;
}
if (source != NULL && target == NULL && is_mountpoint(source)) {
ret = umount2(source, MNT_DETACH);
if (ret < 0) {
ret = -errno;
log_errno("umount2(%s)", source);
goto out;
}
}
ret = sys_move_mount(tree_fd, "", -EBADF, target == NULL ?
source : target, MOVE_MOUNT_F_EMPTY_PATH);
if (ret < 0) {
ret = -errno;
log_errno("sys_move_mount(%s)", target == NULL ?
source : target);
}
out:
if (tree_fd > 0)
close(tree_fd);
if (source_fd > 0)
close(source_fd);
if (attr.userns_fd > 0)
close(attr.userns_fd);
return (ret);
}
static void
print_usage(char *argv[])
{
fprintf(stderr, "Usage: %s [-r] [-c] [-m <idmap1>] [-m <idmap2>]" \
" ... [<source>] [<target>]\n", argv[0]);
fprintf(stderr, "\n");
fprintf(stderr, " -r Recursively do idmapped mount.\n");
fprintf(stderr, "\n");
fprintf(stderr, " -c Checks if idmapped mount is supported " \
"on the <source> by the operating system or not.\n");
fprintf(stderr, "\n");
fprintf(stderr, " -m <idmap> to specify the idmap info, " \
"in the following format:\n");
fprintf(stderr, " <id_type>:<nsid_first>:<hostid_first>:<count>\n");
fprintf(stderr, "\n");
fprintf(stderr, " <id_type> can be either of 'b', 'u', and 'g'.\n");
fprintf(stderr, "\n");
fprintf(stderr, "The <source> folder will be mounted at <target> " \
"with the provided idmap information.\nIf no <target> is " \
"specified, and <source> is a mount point, " \
"then <source> will be unmounted and then remounted.\n");
}
int
main(int argc, char *argv[])
{
int opt;
list_t idmap_head;
int check_supported = 0;
int ret = EXIT_SUCCESS;
char *source = NULL, *target = NULL;
int flags = 0;
list_create(&idmap_head, sizeof (struct idmap_entry),
offsetof(struct idmap_entry, node));
while ((opt = getopt(argc, argv, "rcm:")) != -1) {
switch (opt) {
case 'r':
flags |= AT_RECURSIVE;
break;
case 'c':
check_supported = 1;
break;
case 'm':
ret = parse_idmap_entry(&idmap_head, optarg);
if (ret) {
errno = ret;
log_errno("parse_idmap_entry(%s)", optarg);
ret = EXIT_FAILURE;
goto out;
}
break;
default:
print_usage(argv);
exit(EXIT_FAILURE);
}
}
if (check_supported == 0 && list_is_empty(&idmap_head)) {
print_usage(argv);
ret = EXIT_FAILURE;
goto out;
}
if (optind >= argc) {
fprintf(stderr, "Expected to have <source>, <target>.\n");
print_usage(argv);
ret = EXIT_FAILURE;
goto out;
}
source = argv[optind];
if (optind < (argc - 1)) {
target = argv[optind + 1];
}
if (check_supported) {
free_idmap(&idmap_head);
list_destroy(&idmap_head);
if (is_idmap_supported(source)) {
printf("idmapped mount is supported on [%s].\n",
source);
return (EXIT_SUCCESS);
} else {
printf("idmapped mount is NOT supported.\n");
return (EXIT_FAILURE);
}
}
ret = do_idmap_mount(&idmap_head, source, target, flags);
if (ret)
ret = EXIT_FAILURE;
out:
free_idmap(&idmap_head);
list_destroy(&idmap_head);
exit(ret);
}
+4 -2
View File
@@ -156,7 +156,8 @@ export SYSTEM_FILES_LINUX='attr
useradd
userdel
usermod
setpriv
mountpoint
flock
logger'
@@ -226,4 +227,5 @@ export ZFSTEST_FILES='badsend
truncate_test
ereports
zfs_diff-socket
dosmode_readonly_write'
dosmode_readonly_write
idmap_util'
+10 -2
View File
@@ -378,7 +378,9 @@ nobase_dist_datadir_zfs_tests_tests_DATA += \
functional/zvol/zvol_common.shlib \
functional/zvol/zvol_ENOSPC/zvol_ENOSPC.cfg \
functional/zvol/zvol_misc/zvol_misc_common.kshlib \
functional/zvol/zvol_swap/zvol_swap.cfg
functional/zvol/zvol_swap/zvol_swap.cfg \
functional/idmap_mount/idmap_mount.cfg \
functional/idmap_mount/idmap_mount_common.kshlib
nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
functional/acl/off/cleanup.ksh \
@@ -1998,4 +2000,10 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
functional/zvol/zvol_swap/zvol_swap_003_pos.ksh \
functional/zvol/zvol_swap/zvol_swap_004_pos.ksh \
functional/zvol/zvol_swap/zvol_swap_005_pos.ksh \
functional/zvol/zvol_swap/zvol_swap_006_pos.ksh
functional/zvol/zvol_swap/zvol_swap_006_pos.ksh \
functional/idmap_mount/cleanup.ksh \
functional/idmap_mount/setup.ksh \
functional/idmap_mount/idmap_mount_001.ksh \
functional/idmap_mount/idmap_mount_002.ksh \
functional/idmap_mount/idmap_mount_003.ksh \
functional/idmap_mount/idmap_mount_004.ksh
+25
View File
@@ -0,0 +1,25 @@
#!/bin/ksh -p
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or https://opensource.org/licenses/CDDL-1.0.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
. $STF_SUITE/include/libtest.shlib
default_cleanup
@@ -0,0 +1,25 @@
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or https://opensource.org/licenses/CDDL-1.0.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
export UID1=1000000
export GID1=1000000
export UID2=2000000
export GID2=2000000
@@ -0,0 +1,76 @@
#!/bin/ksh -p
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or https://opensource.org/licenses/CDDL-1.0.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
. $STF_SUITE/tests/functional/idmap_mount/idmap_mount_common.kshlib
#
#
# DESCRIPTION:
# Test uid and gid of files in idmapped folder are mapped correctly
#
#
# STRATEGY:
# 1. Create files/folder owned by $UID1 and $GID1 under "idmap_test"
# 2. Idmap the folder to "idmap_dest"
# 3. Verify the owner of files/folder under "idmap_dest"
#
verify_runnable "global"
export WORKDIR=$TESTDIR/idmap_test
export IDMAPDIR=$TESTDIR/idmap_dest
function cleanup
{
log_must rm -rf $WORKDIR
if mountpoint $IDMAPDIR; then
log_must umount $IDMAPDIR
fi
log_must rm -rf $IDMAPDIR
}
log_onexit cleanup
if ! idmap_util -c $TESTDIR; then
log_unsupported "Idmap mount not supported."
fi
log_must mkdir -p $WORKDIR
log_must mkdir -p $IDMAPDIR
log_must touch $WORKDIR/file1
log_must mkdir $WORKDIR/subdir
log_must ln -s $WORKDIR/file1 $WORKDIR/file1_sym
log_must ln $WORKDIR/file1 $WORKDIR/subdir/file1_hard
log_must touch $WORKDIR/subdir/file2
log_must chown -R $UID1:$GID1 $WORKDIR
log_must chown $UID2:$GID2 $WORKDIR/subdir/file2
log_must idmap_util -m "u:${UID1}:${UID2}:1" -m "g:${GID1}:${GID2}:1" $WORKDIR $IDMAPDIR
log_must test "$UID2 $GID2" = "$(stat -c '%u %g' $IDMAPDIR/file1)"
log_must test "$UID2 $GID2" = "$(stat -c '%u %g' $IDMAPDIR/file1_sym)"
log_must test "$UID2 $GID2" = "$(stat -c '%u %g' $IDMAPDIR/subdir)"
log_must test "$UID2 $GID2" = "$(stat -c '%u %g' $IDMAPDIR/subdir/file1_hard)"
log_mustnot test "$UID2 $GID2" = "$(stat -c '%u %g' $IDMAPDIR/subdir/file2)"
log_pass "Owner verification of entries under idmapped folder is successful."
@@ -0,0 +1,97 @@
#!/bin/ksh -p
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or https://opensource.org/licenses/CDDL-1.0.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
. $STF_SUITE/tests/functional/idmap_mount/idmap_mount_common.kshlib
#
#
# DESCRIPTION:
# Perform file operations in idmapped folder, check owner in its base.
#
#
# STRATEGY:
# 1. Create folder "idmap_test"
# 2. Idmap the folder to "idmap_dest"
# 3. Do basic file operations in "idmap_dest" folder, verify the owner in
# the base folder "idmap_test"
#
verify_runnable "global"
export WORKDIR=$TESTDIR/idmap_test
export IDMAPDIR=$TESTDIR/idmap_dest
function cleanup
{
log_must rm -rf $IDMAPDIR/*
if mountpoint $IDMAPDIR; then
log_must umount $IDMAPDIR
fi
log_must rm -rf $IDMAPDIR $WORKDIR
}
log_onexit cleanup
if ! idmap_util -c $TESTDIR; then
log_unsupported "Idmap mount not supported."
fi
log_must mkdir -p $WORKDIR
log_must mkdir -p $IDMAPDIR
log_must chown $UID1:$GID1 $WORKDIR
log_must idmap_util -m "u:${UID1}:${UID2}:1" -m "g:${GID1}:${GID2}:1" $WORKDIR $IDMAPDIR
SETPRIV="setpriv --reuid $UID2 --regid $GID2 --clear-groups"
log_must $SETPRIV touch $IDMAPDIR/file1
log_must test "$UID1 $GID1" = "$(stat -c '%u %g' $WORKDIR/file1)"
log_must $SETPRIV mv $IDMAPDIR/file1 $IDMAPDIR/file1_renamed
log_must test "$UID1 $GID1" = "$(stat -c '%u %g' $WORKDIR/file1_renamed)"
log_must $SETPRIV mv $IDMAPDIR/file1_renamed $IDMAPDIR/file1
log_must test "$UID1 $GID1" = "$(stat -c '%u %g' $WORKDIR/file1)"
log_must $SETPRIV mkdir $IDMAPDIR/subdir
log_must test "$UID1 $GID1" = "$(stat -c '%u %g' $WORKDIR/subdir)"
log_must $SETPRIV ln -s $IDMAPDIR/file1 $IDMAPDIR/file1_sym
log_must test "$UID1 $GID1" = "$(stat -c '%u %g' $WORKDIR/file1_sym)"
log_must $SETPRIV ln $IDMAPDIR/file1 $IDMAPDIR/subdir/file1_hard
log_must test "$UID1 $GID1" = "$(stat -c '%u %g' $WORKDIR/subdir/file1_hard)"
log_must $SETPRIV touch $IDMAPDIR/subdir/file2
log_must $SETPRIV chown $UID2:$GID2 $IDMAPDIR/subdir/file2
log_mustnot $SETPRIV chown $UID1 $IDMAPDIR/subdir/file2
log_must $SETPRIV cp -r $IDMAPDIR/subdir $IDMAPDIR/subdir1
log_must test "$UID1 $GID1" = "$(stat -c '%u %g' $WORKDIR/subdir1/file2)"
log_must $SETPRIV rm -rf $IDMAPDIR/subdir1
log_must $SETPRIV cp -rp $IDMAPDIR/subdir $IDMAPDIR/subdir1
log_must test "$UID1 $GID1" = "$(stat -c '%u %g' $WORKDIR/subdir1/file1_hard)"
log_must $SETPRIV rm -rf $IDMAPDIR/subdir1
log_pass "Owner verification of entries under base folder is successful."
@@ -0,0 +1,121 @@
#!/bin/ksh -p
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or https://opensource.org/licenses/CDDL-1.0.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
. $STF_SUITE/tests/functional/idmap_mount/idmap_mount_common.kshlib
#
#
# DESCRIPTION:
# Perform file operations in idmapped folder in user namespace,
# then check the owner in its base.
#
#
# STRATEGY:
# 1. Create folder "idmap_test"
# 2. Idmap the folder to "idmap_dest"
# 3. Perform file operations in the idmapped folder in the user
# namespace having the same idmap info as the idmapped mount
# 4. Verify the owner of entries under the base folder "idmap_test"
#
verify_runnable "global"
export WORKDIR=$TESTDIR/idmap_test
export IDMAPDIR=$TESTDIR/idmap_dest
function cleanup
{
kill -TERM ${unshared_pid}
log_must rm -rf $IDMAPDIR/*
if mountpoint $IDMAPDIR; then
log_must umount $IDMAPDIR
fi
log_must rm -rf $IDMAPDIR $WORKDIR
}
log_onexit cleanup
if ! idmap_util -c $TESTDIR; then
log_unsupported "Idmap mount not supported."
fi
log_must mkdir -p $WORKDIR
log_must mkdir -p $IDMAPDIR
log_must chown $UID1:$GID1 $WORKDIR
log_must idmap_util -m "u:${UID1}:${UID2}:1" -m "g:${GID1}:${GID2}:1" $WORKDIR $IDMAPDIR
# Create a user namespace with the same idmapping
unshare -Urm echo test
if [ "$?" -ne "0" ]; then
log_unsupported "Failed to create user namespace"
fi
unshare -Um /usr/bin/sleep 2h &
unshared_pid=$!
if [ "$?" -ne "0" ]; then
log_unsupported "Failed to create user namespace"
fi
# wait for userns to be ready
sleep 1
echo "${UID1} ${UID2} 1" > /proc/$unshared_pid/uid_map
if [ "$?" -ne "0" ]; then
log_unsupported "Failed to write to uid_map"
fi
echo "${GID1} ${GID2} 1" > /proc/$unshared_pid/gid_map
if [ "$?" -ne "0" ]; then
log_unsupported "Failed to write to gid_map"
fi
NSENTER="nsenter -t $unshared_pid --all -S ${UID1} -G ${GID1}"
log_must $NSENTER touch $IDMAPDIR/file1
log_must test "$UID1 $GID1" = "$(stat -c '%u %g' $WORKDIR/file1)"
log_must $NSENTER mv $IDMAPDIR/file1 $IDMAPDIR/file1_renamed
log_must test "$UID1 $GID1" = "$(stat -c '%u %g' $WORKDIR/file1_renamed)"
log_must $NSENTER mv $IDMAPDIR/file1_renamed $IDMAPDIR/file1
log_must test "$UID1 $GID1" = "$(stat -c '%u %g' $WORKDIR/file1)"
log_must $NSENTER mkdir $IDMAPDIR/subdir
log_must test "$UID1 $GID1" = "$(stat -c '%u %g' $WORKDIR/subdir)"
log_must $NSENTER ln -s $IDMAPDIR/file1 $IDMAPDIR/file1_sym
log_must test "$UID1 $GID1" = "$(stat -c '%u %g' $WORKDIR/file1_sym)"
log_must $NSENTER ln $IDMAPDIR/file1 $IDMAPDIR/subdir/file1_hard
log_must test "$UID1 $GID1" = "$(stat -c '%u %g' $WORKDIR/subdir/file1_hard)"
log_must $NSENTER touch $IDMAPDIR/subdir/file2
log_must $NSENTER chown $UID1:$GID1 $IDMAPDIR/subdir/file2
log_mustnot $NSENTER chown $UID2 $IDMAPDIR/subdir/file2
log_must $NSENTER cp -r $IDMAPDIR/subdir $IDMAPDIR/subdir1
log_must test "$UID1 $GID1" = "$(stat -c '%u %g' $WORKDIR/subdir1/file2)"
log_must $NSENTER rm -rf $IDMAPDIR/subdir1
log_must $NSENTER cp -rp $IDMAPDIR/subdir $IDMAPDIR/subdir1
log_must test "$UID1 $GID1" = "$(stat -c '%u %g' $WORKDIR/subdir1/file1_hard)"
log_must $NSENTER rm -rf $IDMAPDIR/subdir1
log_pass "Owner verification of entries under the base folder is successful."
@@ -0,0 +1,106 @@
#!/bin/ksh -p
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or https://opensource.org/licenses/CDDL-1.0.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
. $STF_SUITE/tests/functional/idmap_mount/idmap_mount_common.kshlib
#
#
# DESCRIPTION:
# Test setgid bit is set properly on the idmapped mount
# in a user namespace.
#
# STRATEGY:
# 1. Create folder "idmap_test", set gid bit on it
# 2. Idmap the folder to "idmap_dest"
# 3. Create file and folder in the idmapped folder in the user
# namespace having the same idmap info
# 4. Verify the gid bit of the file and folder is set
#
verify_runnable "global"
export WORKDIR=$TESTDIR/idmap_test
export IDMAPDIR=$TESTDIR/idmap_dest
function cleanup
{
kill -TERM ${unshared_pid}
log_must rm -rf $IDMAPDIR/*
if mountpoint $IDMAPDIR; then
log_must umount $IDMAPDIR
fi
log_must rm -rf $IDMAPDIR $WORKDIR
}
log_onexit cleanup
if ! idmap_util -c $TESTDIR; then
log_unsupported "Idmap mount not supported."
fi
log_must mkdir -p $WORKDIR
log_must mkdir -p $IDMAPDIR
log_must chown $UID1:$GID1 $WORKDIR
# set gid bit
log_must chmod 2755 $WORKDIR
log_must idmap_util -m "u:${UID1}:${UID2}:1" -m "g:${GID1}:${GID2}:1" $WORKDIR $IDMAPDIR
log_must test -g $IDMAPDIR
# Create a user namespace with the same idmapping
unshare -Urm echo test
if [ "$?" -ne "0" ]; then
log_unsupported "Failed to create user namespace"
fi
unshare -Um /usr/bin/sleep 2h &
unshared_pid=$!
if [ "$?" -ne "0" ]; then
log_unsupported "Failed to create user namespace"
fi
# wait for userns to be ready
sleep 1
echo "${UID1} ${UID2} 1" > /proc/$unshared_pid/uid_map
if [ "$?" -ne "0" ]; then
log_unsupported "Failed to write to uid_map"
fi
echo "${GID1} ${GID2} 1" > /proc/$unshared_pid/gid_map
if [ "$?" -ne "0" ]; then
log_unsupported "Failed to write to gid_map"
fi
NSENTER="nsenter -t $unshared_pid --all -S ${UID1} -G ${GID1}"
# gid bit can be set on the file
log_must $NSENTER touch $IDMAPDIR/file1
log_must $NSENTER chmod 2654 $IDMAPDIR/file1
log_must test -g $WORKDIR/file1
log_must test -g $IDMAPDIR/file1
log_must test "$UID1 $GID1" = "$($NSENTER stat -c '%u %g' $IDMAPDIR/file1)"
# gid bit is carried over to new folder
log_must $NSENTER mkdir $IDMAPDIR/subdir
log_must test -g $WORKDIR/subdir
log_must test -g $IDMAPDIR/subdir
log_must test "$UID1 $GID1" = "$($NSENTER stat -c '%u %g' $IDMAPDIR/subdir)"
log_pass "Verification of setting gid bit in userns is successful."
@@ -0,0 +1,23 @@
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or https://opensource.org/licenses/CDDL-1.0.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
. $STF_SUITE/include/libtest.shlib
. $STF_SUITE/tests/functional/idmap_mount/idmap_mount.cfg
+30
View File
@@ -0,0 +1,30 @@
#!/bin/ksh -p
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or https://opensource.org/licenses/CDDL-1.0.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
. $STF_SUITE/include/libtest.shlib
# unable to do idmapped mount in a local zone
verify_runnable "global"
DISK=${DISKS%% *}
default_setup $DISK