Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

user namespace bugfixes and features #6865

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 177 additions & 3 deletions module/zfs/zfs_ioctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -2953,6 +2953,121 @@ zfs_ioc_pool_get_props(zfs_cmd_t *zc)
return (error);
}

#ifdef CONFIG_USER_NS
static int
zfs_map_who(char *whobuf, const char *who, struct user_namespace *ns,
boolean_t from_namespace)
{
u_longlong_t id;
int error;

/* copy type, local/desendent flag and ZFS_DELEG_FIELD_SEP_CHR */
whobuf[0] = who[0];
whobuf[1] = who[1];
whobuf[2] = who[2];

/* map the uid and gid parts to/from the user namespace */
switch (whobuf[0]) {
case ZFS_DELEG_USER:
case ZFS_DELEG_USER_SETS:
if ((error = kstrtoull(&who[3], 10, &id)) != 0)
return (error);
if (from_namespace) {
kuid_t kuid = make_kuid(ns, (uid_t)id);
if (!uid_valid(kuid))
return (SET_ERROR(EINVAL));
(void) snprintf(&whobuf[3], ZFS_MAX_DELEG_NAME-3,
"%lld", (longlong_t)__kuid_val(kuid));
} else {
(void) snprintf(&whobuf[3], ZFS_MAX_DELEG_NAME-3,
"%lld", (longlong_t)from_kuid_munged(ns,
KUIDT_INIT(id)));
}
break;
case ZFS_DELEG_GROUP:
case ZFS_DELEG_GROUP_SETS:
if ((error = kstrtoull(&who[3], 10, &id)) != 0)
return (error);
if (from_namespace) {
kgid_t kgid = make_kgid(ns, (gid_t)id);
if (!gid_valid(kgid))
return (SET_ERROR(EINVAL));
(void) snprintf(&whobuf[3], ZFS_MAX_DELEG_NAME-3,
"%lld", (longlong_t)__kgid_val(kgid));
} else {
(void) snprintf(&whobuf[3], ZFS_MAX_DELEG_NAME-3,
"%lld", (longlong_t)from_kgid_munged(ns,
KGIDT_INIT(id)));
}
break;
default:
strlcpy(&whobuf[3], &who[3], ZFS_MAX_DELEG_NAME-3);
break;
}
return (0);
}

/*
* Assumes a correct nvlist (iow. chcked with zfs_deleg_verify_nvlist(), or
* coming from the file system to be mapped into the querying user's namespace.
*/
static int
fsacl_map_user_ns(nvlist_t **pnvp, struct user_namespace *ns,
boolean_t from_namespace)
{
nvlist_t *nvp, *mapped_nvp;
nvpair_t *who;
int error;
char mapped_who[ZFS_MAX_DELEG_NAME];

ASSERT(pnvp);
nvp = *pnvp;
ASSERT(nvp);

who = nvlist_next_nvpair(nvp, NULL);
if (who == NULL)
return (SET_ERROR(ENOENT));

if ((error = nvlist_alloc(&mapped_nvp, NV_UNIQUE_NAME, KM_SLEEP)) != 0)
return (error);

do {
data_type_t type = nvpair_type(who);
char *whoname = nvpair_name(who);
error = zfs_map_who(mapped_who, whoname, ns, from_namespace);
if (error)
goto err;

/*
* A set can be deleted as a whole by adding its name as a
* boolean value.
*/
switch (type) {
case DATA_TYPE_BOOLEAN:
error = nvlist_add_boolean(mapped_nvp, mapped_who);
break;
case DATA_TYPE_NVLIST:
error = nvlist_add_nvlist(mapped_nvp, mapped_who,
fnvpair_value_nvlist(who));
break;
default:
return (SET_ERROR(EINVAL));
}

if (error)
goto err;
} while ((who = nvlist_next_nvpair(nvp, who)) != NULL);

nvlist_free(nvp);
*pnvp = mapped_nvp;
return (0);

err:
nvlist_free(mapped_nvp);
return (error);
}
#endif

/*
* inputs:
* zc_name name of filesystem
Expand All @@ -2979,6 +3094,17 @@ zfs_ioc_set_fsacl(zfs_cmd_t *zc)
return (SET_ERROR(EINVAL));
}

#ifdef CONFIG_USER_NS
/*
* Perform user/group mapping according to the current user namespace.
*/
error = fsacl_map_user_ns(&fsaclnv, current_user_ns(), B_TRUE);
if (error != 0) {
nvlist_free(fsaclnv);
return (error);
}
#endif

/*
* If we don't have PRIV_SYS_MOUNT, then validate
* that user is allowed to hand out each permission in
Expand All @@ -3003,6 +3129,51 @@ zfs_ioc_set_fsacl(zfs_cmd_t *zc)
return (error);
}

#ifdef CONFIG_USER_NS
static int
deleg_map_user_ns(nvlist_t **pnvp)
{
nvlist_t *nvp, *mapped_nvp;
nvpair_t *dditer;
int error;
struct user_namespace *ns = current_user_ns();

ASSERT(pnvp);
nvp = *pnvp;
ASSERT(nvp);

dditer = nvlist_next_nvpair(nvp, NULL);
if (dditer == NULL)
return (0);

if ((error = nvlist_alloc(&mapped_nvp, NV_UNIQUE_NAME, KM_SLEEP)) != 0)
return (error);

do {
nvlist_t *entry;
char *ddname = nvpair_name(dditer);
if ((error = nvpair_value_nvlist(dditer, &entry)) != 0)
goto err;

if ((error = fsacl_map_user_ns(&entry, ns, B_FALSE)) != 0)
goto err;

error = nvlist_add_nvlist(mapped_nvp, ddname, entry);
nvlist_free(entry);
if (error)
goto err;
} while ((dditer = nvlist_next_nvpair(nvp, dditer)) != NULL);

nvlist_free(nvp);
*pnvp = mapped_nvp;
return (0);

err:
nvlist_free(mapped_nvp);
return (error);
}
#endif

/*
* inputs:
* zc_name name of filesystem
Expand All @@ -3016,10 +3187,13 @@ zfs_ioc_get_fsacl(zfs_cmd_t *zc)
nvlist_t *nvp;
int error;

if ((error = dsl_deleg_get(zc->zc_name, &nvp)) == 0) {
if ((error = dsl_deleg_get(zc->zc_name, &nvp)) != 0)
return (error);
#ifdef CONFIG_USER_NS
if ((error = deleg_map_user_ns(&nvp)) == 0)
error = put_nvlist(zc, nvp);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need this put_nvlist in the !CONFIG_USER_NS case as well.

nvlist_free(nvp);
}
#endif
nvlist_free(nvp);

return (error);
}
Expand Down
2 changes: 1 addition & 1 deletion tests/runfiles/linux.run
Original file line number Diff line number Diff line change
Expand Up @@ -697,7 +697,7 @@ tags = ['functional', 'upgrade']

# user_namespace_001 - https://github.com/zfsonlinux/zfs/issues/6800
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't need to link to the issue once this functionality works and has test coverage. So this can be dropped.

[tests/functional/user_namespace]
tests = ['user_namespace_001', 'user_namespace_002']
tests = ['user_namespace_001', 'user_namespace_002', 'user_namespace_003']
tags = ['functional', 'user_namespace']

[tests/functional/userquota]
Expand Down
3 changes: 2 additions & 1 deletion tests/zfs-tests/tests/functional/user_namespace/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ dist_pkgdata_SCRIPTS = \
setup.ksh \
cleanup.ksh \
user_namespace_001.ksh \
user_namespace_002.ksh
user_namespace_002.ksh \
user_namespace_003.ksh
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ export ROOT_UID=100000
export OTHER_UID=101000
export USER_TESTFS=$TESTPOOL/$TESTFS/user
export USER_TESTDIR=$TESTDIR/user
export STAFF_USER=zfsusr
export STAFF_GROUP=zfsgrp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#!/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 http://www.opensolaris.org/os/licensing.
# 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/user_namespace/user_namespace_common.kshlib

#
#
# DESCRIPTION:
# Test mapping of users when using 'zfs allow'.
#
#
# STRATEGY:
# 1. Create datasets for users.
# 2. Delegate permissions to the unprivileged root user.
# 3. Verify 'zfs allow' that inside the namespace shows the correct user.
# 4. Verify that running 'zfs allow' inside a user namespace maps uids.
#

verify_runnable "both"

log_must add_group $STAFF_GROUP
log_must add_user $STAFF_GROUP $STAFF_USER
function cleanup
{
log_must del_user $STAFF_USER
log_must del_group $STAFF_GROUP
}
log_onexit cleanup

log_assert "Check user mapping in user namespaces"

# Test with a permission which needs no extra capabilities such as mounting...
typeset perm="snapshot_limit"
typeset perm_state_1="none"
typeset perm_state_2="3"

log_must zfs create -o ${perm}=${perm_state_1} $USER_TESTFS
log_must chown $ROOT_UID:$ROOT_UID $USER_TESTDIR

# allow the unprivileged root to pass on permissions
log_must zfs allow -u $ROOT_UID ${perm},allow $USER_TESTFS
# from within the user namespace, allow $STAFF_USER to create a dataset
log_must user_ns_exec zfs allow $STAFF_USER ${perm} $USER_TESTFS
# make sure the staff user *outside* the user namespace is not affected
log_mustnot chg_usr_exec $STAFF_USER zfs set ${perm}=${perm_state_2} $USER_TESTFS
# make sure the staff user *inside* the user namespace functions as expected
log_must user_ns_exec chg_usr_exec $STAFF_USER zfs set ${perm}=${perm_state_2} $USER_TESTFS

log_must zfs destroy -r $USER_TESTFS

log_pass "Check user mapping in user namespaces"