Skip to content

Commit

Permalink
[vpsAdminOS] Configurable UID/GID maps
Browse files Browse the repository at this point in the history
This patch introduces two new properties: uidmap and gidmap.
These properties are used to map user and group IDs to a different
namespace. The mapping occurs only in memory and what is presented to VFS.
On disk, the IDs are not shifted, which means the map can be changed
at will without chowning all the files within the dataset.

This feature is useful for unprivileged LXC containers that are using
UID/GID mapping. Create the container _without_ any idmap, then
configure the mapping in LXC and set appropriate uidmap and gidmap
on the container's dataset.

For example:

  zfs set uidmap="0:100000:65536" tank/test
  zfs set gidmap="0:100000:65536" tank/test

The commands above will make it so that UID/GID 0-65535 is mapped to
100000-165535. To use this dataset in a user namespace, you could do
something like `newuidmap <pid> 0 100000 65536` or in LXC
`lxc.idmap = u 0 100000 65536` and the same for group IDs.

ZFS checks that the map is syntactically correct and can process up to
10 map entries separated by commas. However, it does not check whether
the mapping makes sense logically, or if there aren't some overlaps. Map
entries are searched for sequentially, first matching entry wins.
  • Loading branch information
aither64 authored and snajpa committed Oct 4, 2019
1 parent cc8317c commit e472c16
Show file tree
Hide file tree
Showing 21 changed files with 672 additions and 12 deletions.
1 change: 1 addition & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,7 @@ AC_CONFIG_FILES([
tests/zfs-tests/tests/functional/trim/Makefile
tests/zfs-tests/tests/functional/truncate/Makefile
tests/zfs-tests/tests/functional/user_namespace/Makefile
tests/zfs-tests/tests/functional/ugid_maps/Makefile
tests/zfs-tests/tests/functional/userquota/Makefile
tests/zfs-tests/tests/functional/upgrade/Makefile
tests/zfs-tests/tests/functional/vdev_zaps/Makefile
Expand Down
3 changes: 3 additions & 0 deletions include/os/linux/zfs/sys/zfs_vfsops.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include <sys/dsl_dataset.h>
#include <sys/zfs_ioctl.h>
#include <sys/objlist.h>
#include <sys/zfs_ugid_map.h>

#ifdef __cplusplus
extern "C" {
Expand Down Expand Up @@ -135,6 +136,8 @@ struct zfsvfs {
avl_tree_t *z_hold_trees; /* znode hold trees */
kmutex_t *z_hold_locks; /* znode hold locks */
taskqid_t z_drain_task; /* task id for the unlink drain task */
struct zfs_ugid_map *z_uid_map;
struct zfs_ugid_map *z_gid_map;
};

#define ZSB_XATTR 0x0001 /* Enable user xattrs */
Expand Down
1 change: 1 addition & 0 deletions include/sys/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ COMMON_H = \
$(top_srcdir)/include/sys/zfs_stat.h \
$(top_srcdir)/include/sys/zfs_sysfs.h \
$(top_srcdir)/include/sys/zfs_znode.h \
$(top_srcdir)/include/sys/zfs_ugid_map.h \
$(top_srcdir)/include/sys/zil.h \
$(top_srcdir)/include/sys/zil_impl.h \
$(top_srcdir)/include/sys/zio_checksum.h \
Expand Down
2 changes: 2 additions & 0 deletions include/sys/fs/zfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ typedef enum {
ZFS_PROP_IVSET_GUID, /* not exposed to the user */
ZFS_PROP_REDACTED,
ZFS_PROP_REDACT_SNAPS,
ZFS_PROP_UIDMAP,
ZFS_PROP_GIDMAP,
ZFS_NUM_PROPS
} zfs_prop_t;

Expand Down
23 changes: 23 additions & 0 deletions include/sys/zfs_ugid_map.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#ifndef _SYS_FS_ZFS_UGID_MAP_H
#define _SYS_FS_ZFS_UGID_MAP_H

#define ZFS_UGID_MAP_SIZE 10

struct zfs_ugid_map_entry {
uint64_t e_ns_id;
uint64_t e_host_id;
uint64_t e_count;
};

struct zfs_ugid_map {
struct zfs_ugid_map_entry **m_map;
uint64_t m_size;
uint64_t m_entries;
};

struct zfs_ugid_map* zfs_create_ugid_map(objset_t *os, zfs_prop_t prop);
void zfs_free_ugid_map(struct zfs_ugid_map *ugid_map);
uint64_t zfs_ugid_map_ns_to_host(struct zfs_ugid_map *ugid_map, uint64_t id);
uint64_t zfs_ugid_map_host_to_ns(struct zfs_ugid_map *ugid_map, uint64_t id);

#endif /* _SYS_FS_ZFS_UGID_MAP_H */
68 changes: 68 additions & 0 deletions lib/libzfs/libzfs_dataset.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
#include <sys/spa.h>
#include <sys/zap.h>
#include <sys/dsl_crypt.h>
#include <sys/zfs_ugid_map.h>
#include <libzfs.h>
#include <libzutil.h>

Expand Down Expand Up @@ -993,6 +994,45 @@ zfs_which_resv_prop(zfs_handle_t *zhp, zfs_prop_t *resv_prop)
return (0);
}

static int
zfs_ugid_map_validate_str(char *strval)
{
int pos = 0, i = 0, entries = 0, matches;
int64_t ns_id, host_id, cnt;

if (strcmp(strval, "none") == 0)
return 0;

while (1) {
matches = sscanf(strval + pos, "%ld:%ld:%ld%n",
&ns_id, &host_id, &cnt, &i);
pos += i;

if (matches == 0) {
if (entries > 0)
break;
else
return 1;
} else if (matches != 3) {
return 1;
}

if (ns_id < 0 || host_id < 0 || cnt < 1)
return 1;
else if (strval[pos] == ',')
pos += 1;
else if (strval[pos] == '\0')
return 0;
else
return 1;

if (++entries == ZFS_UGID_MAP_SIZE)
return 1;
}

return 0;
}

/*
* Given an nvlist of properties to set, validates that they are correct, and
* parses any numeric properties (index, boolean, etc) if they are specified as
Expand Down Expand Up @@ -1517,6 +1557,31 @@ zfs_valid_proplist(libzfs_handle_t *hdl, zfs_type_t type, nvlist_t *nvl,
chosen_normal = (int)intval;
break;

case ZFS_PROP_UIDMAP:
case ZFS_PROP_GIDMAP:
{
if (zfs_ugid_map_validate_str(strval) != 0) {
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"'%s' is not in the expected format"),
propname);
(void) zfs_error(hdl, EZFS_BADPROP, errbuf);
goto error;
}

if (zhp == NULL)
break;

if (zfs_prop_get_int(zhp, ZFS_PROP_MOUNTED)) {
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"'%s' cannot be changed while the file "
"system is mounted"), propname);
(void) zfs_error(hdl, EZFS_BADPROP, errbuf);
goto error;
}

break;
}

default:
break;
}
Expand Down Expand Up @@ -3052,6 +3117,9 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen,
zcp_check(zhp, prop, val, NULL);
break;

//case ZFS_PROP_UIDMAP: // TODO retrieve property
//case ZFS_PROP_GIDMAP:

default:
switch (zfs_prop_get_type(prop)) {
case PROP_TYPE_NUMBER:
Expand Down
1 change: 1 addition & 0 deletions module/os/linux/zfs/Makefile.in
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ $(MODULE)-objs += ../os/linux/zfs/zfs_debug.o
$(MODULE)-objs += ../os/linux/zfs/zfs_dir.o
$(MODULE)-objs += ../os/linux/zfs/zfs_ioctl_os.o
$(MODULE)-objs += ../os/linux/zfs/zfs_sysfs.o
$(MODULE)-objs += ../os/linux/zfs/zfs_ugid_map.o
$(MODULE)-objs += ../os/linux/zfs/zfs_vfsops.o
$(MODULE)-objs += ../os/linux/zfs/zfs_vnops.o
$(MODULE)-objs += ../os/linux/zfs/zfs_znode.o
Expand Down
183 changes: 183 additions & 0 deletions module/os/linux/zfs/zfs_ugid_map.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
#include <sys/types.h>
#include <sys/fs/zfs.h>
#include <sys/dsl_prop.h>
#include <sys/dsl_dataset.h>
#include <sys/zap.h>
#include <sys/dmu_objset.h>
#include <sys/zfs_ugid_map.h>


struct zfs_ugid_map*
zfs_create_ugid_map(objset_t *os, zfs_prop_t prop)
{
char *value = kmem_alloc(ZAP_MAXVALUELEN, KM_SLEEP);
char source[ZFS_MAX_DATASET_NAME_LEN] = "Internal error - setpoint not determined";
uint32_t ns_id, host_id, count;
int pos = 0, i = 0, error;
struct zfs_ugid_map *ugid_map;
struct zfs_ugid_map_entry *entry;

dsl_pool_config_enter(dmu_objset_pool(os), FTAG);

error = dsl_prop_get_ds(os->os_dsl_dataset, zfs_prop_to_name(prop), 1,
ZAP_MAXVALUELEN, value, source);

dsl_pool_config_exit(dmu_objset_pool(os), FTAG);

if (error != 0) {
kmem_free(value, ZAP_MAXVALUELEN);
/*
* TODO: should we report error? we'd have to pass the return
* value through function argument to be able to report errors
*/
return NULL;
//return (error);
}

if (strcmp(value, "none") == 0)
return NULL;

ugid_map = vmem_zalloc(sizeof(struct zfs_ugid_map), KM_SLEEP);
ugid_map->m_size = ZFS_UGID_MAP_SIZE;
ugid_map->m_entries = 0;
ugid_map->m_map = vmem_zalloc(sizeof(struct zfs_ugid_map_entry*) * ugid_map->m_size,
KM_SLEEP);

while (1) {
error = sscanf(value + pos, "%u:%u:%u%n", &ns_id, &host_id, &count, &i);
pos += i;

if (error == 0) {
break;

} else if (error != 3) {
#ifdef DEBUG
printk("invalid ugid map format");
#endif
return NULL;
//return (error);
}
#ifdef DEBUG
printk("got map: ns_id=%u, host_id=%u,, count=%u for %s", ns_id, host_id, count, source);
#endif

entry = vmem_zalloc(sizeof(struct zfs_ugid_map_entry), KM_SLEEP);
entry->e_ns_id = ns_id;
entry->e_host_id = host_id;
entry->e_count = count;

ugid_map->m_map[ugid_map->m_entries] = entry;
ugid_map->m_entries += 1;

// TODO: make map size dynamic
if (ugid_map->m_entries == ZFS_UGID_MAP_SIZE)
break;
else if (value[pos] == ',')
pos += 1;
else
break;
}

if (ugid_map->m_entries == 0) {
vmem_free(ugid_map->m_map, sizeof(struct zfs_ugid_map_entry*) * ugid_map->m_size);
vmem_free(ugid_map, sizeof(struct zfs_ugid_map));
return NULL;
}

kmem_free(value, ZAP_MAXVALUELEN);
return ugid_map;
}

void
zfs_free_ugid_map(struct zfs_ugid_map *ugid_map)
{
int i;

if (ugid_map == NULL)
return;

for (i = 0; i < ugid_map->m_size; i++) {
vmem_free(ugid_map->m_map[i], sizeof(struct zfs_ugid_map_entry));
}

vmem_free(ugid_map->m_map, sizeof(struct zfs_ugid_map_entry*) * ugid_map->m_size);
vmem_free(ugid_map, sizeof(struct zfs_ugid_map));
}

uint64_t
zfs_ugid_map_ns_to_host(struct zfs_ugid_map *ugid_map, uint64_t id)
{
uint64_t res;
int i;
struct zfs_ugid_map_entry *entry;

if (ugid_map == NULL)
return id;

/* look for a matching mapping */
for (i = 0; i < ugid_map->m_entries; i++) {
entry = ugid_map->m_map[i];

/* check if we're already mapped into the entry */
if (id >= entry->e_host_id && id < (entry->e_host_id + entry->e_count)) {
#ifdef DEBUG
printk("zfs_ugid_map_ns_to_host: %lld already mapped via mapping %lld:%lld:%lld",
id, entry->e_ns_id, entry->e_host_id, entry->e_count);
#endif
return id;
}

/* check if we can map the entry */
if (id >= entry->e_ns_id && id < (entry->e_ns_id + entry->e_count)) {
res = entry->e_host_id + (id - entry->e_ns_id);
#ifdef DEBUG
printk("zfs_ugid_map_ns_to_host: %lld -> %lld via mapping %lld:%lld:%lld",
id, res, entry->e_ns_id, entry->e_host_id, entry->e_count);
#endif
VERIFY3U(0, <=, res);
return res;
}
}

/* id not mapped, return nobody */
return 65534;
}

uint64_t
zfs_ugid_map_host_to_ns(struct zfs_ugid_map *ugid_map, uint64_t id)
{
uint64_t res;
int i;
struct zfs_ugid_map_entry *entry;

if (ugid_map == NULL)
return id;

/* look for a matching mapping */
for (i = 0; i < ugid_map->m_entries; i++) {
entry = ugid_map->m_map[i];

/* check if we're already mapped into the entry */
if (id >= entry->e_ns_id && id < (entry->e_ns_id + entry->e_count)) {
#ifdef DEBUG
printk("zfs_ugid_map_host_to_ns: %lld already mapped via mapping %lld:%lld:%lld",
id, entry->e_ns_id, entry->e_host_id, entry->e_count);
#endif
return id;
}

/* check if we can map the entry */
if (id >= entry->e_host_id && id < (entry->e_host_id + entry->e_count)) {
res = (id - entry->e_host_id) + entry->e_ns_id;
#ifdef DEBUG
printk("zfs_ugid_map_host_to_ns: %lld -> %lld via mapping %lld:%lld:%lld",
id, res, entry->e_ns_id, entry->e_host_id, entry->e_count);
#endif
VERIFY3U(0, <=, res);
return res;
}
}

/* id not mapped, return nobody */
return 65534;
}
6 changes: 6 additions & 0 deletions module/os/linux/zfs/zfs_vfsops.c
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
#include <sys/objlist.h>
#include <sys/zpl.h>
#include <linux/vfs_compat.h>
#include <sys/zfs_ugid_map.h>
#include "zfs_comutil.h"

enum {
Expand Down Expand Up @@ -1026,6 +1027,9 @@ zfsvfs_init(zfsvfs_t *zfsvfs, objset_t *os)
return (error);
zfsvfs->z_acl_type = (uint_t)val;

zfsvfs->z_uid_map = zfs_create_ugid_map(zfsvfs->z_os, ZFS_PROP_UIDMAP);
zfsvfs->z_gid_map = zfs_create_ugid_map(zfsvfs->z_os, ZFS_PROP_GIDMAP);

/*
* Fold case on file systems that are always or sometimes case
* insensitive.
Expand Down Expand Up @@ -1318,6 +1322,8 @@ zfsvfs_free(zfsvfs_t *zfsvfs)
}
vmem_free(zfsvfs->z_hold_trees, sizeof (avl_tree_t) * size);
vmem_free(zfsvfs->z_hold_locks, sizeof (kmutex_t) * size);
zfs_free_ugid_map(zfsvfs->z_uid_map);
zfs_free_ugid_map(zfsvfs->z_gid_map);
zfsvfs_vfs_free(zfsvfs->z_vfs);
dataset_kstats_destroy(&zfsvfs->z_kstat);
kmem_free(zfsvfs, sizeof (zfsvfs_t));
Expand Down
Loading

0 comments on commit e472c16

Please sign in to comment.