162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/* Copyright (c) 2022 Christian Brauner <brauner@kernel.org> */
362306a36Sopenharmony_ci
462306a36Sopenharmony_ci#include <linux/cred.h>
562306a36Sopenharmony_ci#include <linux/fs.h>
662306a36Sopenharmony_ci#include <linux/mnt_idmapping.h>
762306a36Sopenharmony_ci#include <linux/slab.h>
862306a36Sopenharmony_ci#include <linux/user_namespace.h>
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include "internal.h"
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_cistruct mnt_idmap {
1362306a36Sopenharmony_ci	struct user_namespace *owner;
1462306a36Sopenharmony_ci	refcount_t count;
1562306a36Sopenharmony_ci};
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci/*
1862306a36Sopenharmony_ci * Carries the initial idmapping of 0:0:4294967295 which is an identity
1962306a36Sopenharmony_ci * mapping. This means that {g,u}id 0 is mapped to {g,u}id 0, {g,u}id 1 is
2062306a36Sopenharmony_ci * mapped to {g,u}id 1, [...], {g,u}id 1000 to {g,u}id 1000, [...].
2162306a36Sopenharmony_ci */
2262306a36Sopenharmony_cistruct mnt_idmap nop_mnt_idmap = {
2362306a36Sopenharmony_ci	.owner	= &init_user_ns,
2462306a36Sopenharmony_ci	.count	= REFCOUNT_INIT(1),
2562306a36Sopenharmony_ci};
2662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(nop_mnt_idmap);
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci/**
2962306a36Sopenharmony_ci * check_fsmapping - check whether an mount idmapping is allowed
3062306a36Sopenharmony_ci * @idmap: idmap of the relevent mount
3162306a36Sopenharmony_ci * @sb:    super block of the filesystem
3262306a36Sopenharmony_ci *
3362306a36Sopenharmony_ci * Return: true if @idmap is allowed, false if not.
3462306a36Sopenharmony_ci */
3562306a36Sopenharmony_cibool check_fsmapping(const struct mnt_idmap *idmap,
3662306a36Sopenharmony_ci		     const struct super_block *sb)
3762306a36Sopenharmony_ci{
3862306a36Sopenharmony_ci	return idmap->owner != sb->s_user_ns;
3962306a36Sopenharmony_ci}
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci/**
4262306a36Sopenharmony_ci * initial_idmapping - check whether this is the initial mapping
4362306a36Sopenharmony_ci * @ns: idmapping to check
4462306a36Sopenharmony_ci *
4562306a36Sopenharmony_ci * Check whether this is the initial mapping, mapping 0 to 0, 1 to 1,
4662306a36Sopenharmony_ci * [...], 1000 to 1000 [...].
4762306a36Sopenharmony_ci *
4862306a36Sopenharmony_ci * Return: true if this is the initial mapping, false if not.
4962306a36Sopenharmony_ci */
5062306a36Sopenharmony_cistatic inline bool initial_idmapping(const struct user_namespace *ns)
5162306a36Sopenharmony_ci{
5262306a36Sopenharmony_ci	return ns == &init_user_ns;
5362306a36Sopenharmony_ci}
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci/**
5662306a36Sopenharmony_ci * no_idmapping - check whether we can skip remapping a kuid/gid
5762306a36Sopenharmony_ci * @mnt_userns: the mount's idmapping
5862306a36Sopenharmony_ci * @fs_userns: the filesystem's idmapping
5962306a36Sopenharmony_ci *
6062306a36Sopenharmony_ci * This function can be used to check whether a remapping between two
6162306a36Sopenharmony_ci * idmappings is required.
6262306a36Sopenharmony_ci * An idmapped mount is a mount that has an idmapping attached to it that
6362306a36Sopenharmony_ci * is different from the filsystem's idmapping and the initial idmapping.
6462306a36Sopenharmony_ci * If the initial mapping is used or the idmapping of the mount and the
6562306a36Sopenharmony_ci * filesystem are identical no remapping is required.
6662306a36Sopenharmony_ci *
6762306a36Sopenharmony_ci * Return: true if remapping can be skipped, false if not.
6862306a36Sopenharmony_ci */
6962306a36Sopenharmony_cistatic inline bool no_idmapping(const struct user_namespace *mnt_userns,
7062306a36Sopenharmony_ci				const struct user_namespace *fs_userns)
7162306a36Sopenharmony_ci{
7262306a36Sopenharmony_ci	return initial_idmapping(mnt_userns) || mnt_userns == fs_userns;
7362306a36Sopenharmony_ci}
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci/**
7662306a36Sopenharmony_ci * make_vfsuid - map a filesystem kuid according to an idmapping
7762306a36Sopenharmony_ci * @idmap: the mount's idmapping
7862306a36Sopenharmony_ci * @fs_userns: the filesystem's idmapping
7962306a36Sopenharmony_ci * @kuid : kuid to be mapped
8062306a36Sopenharmony_ci *
8162306a36Sopenharmony_ci * Take a @kuid and remap it from @fs_userns into @idmap. Use this
8262306a36Sopenharmony_ci * function when preparing a @kuid to be reported to userspace.
8362306a36Sopenharmony_ci *
8462306a36Sopenharmony_ci * If no_idmapping() determines that this is not an idmapped mount we can
8562306a36Sopenharmony_ci * simply return @kuid unchanged.
8662306a36Sopenharmony_ci * If initial_idmapping() tells us that the filesystem is not mounted with an
8762306a36Sopenharmony_ci * idmapping we know the value of @kuid won't change when calling
8862306a36Sopenharmony_ci * from_kuid() so we can simply retrieve the value via __kuid_val()
8962306a36Sopenharmony_ci * directly.
9062306a36Sopenharmony_ci *
9162306a36Sopenharmony_ci * Return: @kuid mapped according to @idmap.
9262306a36Sopenharmony_ci * If @kuid has no mapping in either @idmap or @fs_userns INVALID_UID is
9362306a36Sopenharmony_ci * returned.
9462306a36Sopenharmony_ci */
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_civfsuid_t make_vfsuid(struct mnt_idmap *idmap,
9762306a36Sopenharmony_ci				   struct user_namespace *fs_userns,
9862306a36Sopenharmony_ci				   kuid_t kuid)
9962306a36Sopenharmony_ci{
10062306a36Sopenharmony_ci	uid_t uid;
10162306a36Sopenharmony_ci	struct user_namespace *mnt_userns = idmap->owner;
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	if (no_idmapping(mnt_userns, fs_userns))
10462306a36Sopenharmony_ci		return VFSUIDT_INIT(kuid);
10562306a36Sopenharmony_ci	if (initial_idmapping(fs_userns))
10662306a36Sopenharmony_ci		uid = __kuid_val(kuid);
10762306a36Sopenharmony_ci	else
10862306a36Sopenharmony_ci		uid = from_kuid(fs_userns, kuid);
10962306a36Sopenharmony_ci	if (uid == (uid_t)-1)
11062306a36Sopenharmony_ci		return INVALID_VFSUID;
11162306a36Sopenharmony_ci	return VFSUIDT_INIT(make_kuid(mnt_userns, uid));
11262306a36Sopenharmony_ci}
11362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(make_vfsuid);
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci/**
11662306a36Sopenharmony_ci * make_vfsgid - map a filesystem kgid according to an idmapping
11762306a36Sopenharmony_ci * @idmap: the mount's idmapping
11862306a36Sopenharmony_ci * @fs_userns: the filesystem's idmapping
11962306a36Sopenharmony_ci * @kgid : kgid to be mapped
12062306a36Sopenharmony_ci *
12162306a36Sopenharmony_ci * Take a @kgid and remap it from @fs_userns into @idmap. Use this
12262306a36Sopenharmony_ci * function when preparing a @kgid to be reported to userspace.
12362306a36Sopenharmony_ci *
12462306a36Sopenharmony_ci * If no_idmapping() determines that this is not an idmapped mount we can
12562306a36Sopenharmony_ci * simply return @kgid unchanged.
12662306a36Sopenharmony_ci * If initial_idmapping() tells us that the filesystem is not mounted with an
12762306a36Sopenharmony_ci * idmapping we know the value of @kgid won't change when calling
12862306a36Sopenharmony_ci * from_kgid() so we can simply retrieve the value via __kgid_val()
12962306a36Sopenharmony_ci * directly.
13062306a36Sopenharmony_ci *
13162306a36Sopenharmony_ci * Return: @kgid mapped according to @idmap.
13262306a36Sopenharmony_ci * If @kgid has no mapping in either @idmap or @fs_userns INVALID_GID is
13362306a36Sopenharmony_ci * returned.
13462306a36Sopenharmony_ci */
13562306a36Sopenharmony_civfsgid_t make_vfsgid(struct mnt_idmap *idmap,
13662306a36Sopenharmony_ci		     struct user_namespace *fs_userns, kgid_t kgid)
13762306a36Sopenharmony_ci{
13862306a36Sopenharmony_ci	gid_t gid;
13962306a36Sopenharmony_ci	struct user_namespace *mnt_userns = idmap->owner;
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	if (no_idmapping(mnt_userns, fs_userns))
14262306a36Sopenharmony_ci		return VFSGIDT_INIT(kgid);
14362306a36Sopenharmony_ci	if (initial_idmapping(fs_userns))
14462306a36Sopenharmony_ci		gid = __kgid_val(kgid);
14562306a36Sopenharmony_ci	else
14662306a36Sopenharmony_ci		gid = from_kgid(fs_userns, kgid);
14762306a36Sopenharmony_ci	if (gid == (gid_t)-1)
14862306a36Sopenharmony_ci		return INVALID_VFSGID;
14962306a36Sopenharmony_ci	return VFSGIDT_INIT(make_kgid(mnt_userns, gid));
15062306a36Sopenharmony_ci}
15162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(make_vfsgid);
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci/**
15462306a36Sopenharmony_ci * from_vfsuid - map a vfsuid into the filesystem idmapping
15562306a36Sopenharmony_ci * @idmap: the mount's idmapping
15662306a36Sopenharmony_ci * @fs_userns: the filesystem's idmapping
15762306a36Sopenharmony_ci * @vfsuid : vfsuid to be mapped
15862306a36Sopenharmony_ci *
15962306a36Sopenharmony_ci * Map @vfsuid into the filesystem idmapping. This function has to be used in
16062306a36Sopenharmony_ci * order to e.g. write @vfsuid to inode->i_uid.
16162306a36Sopenharmony_ci *
16262306a36Sopenharmony_ci * Return: @vfsuid mapped into the filesystem idmapping
16362306a36Sopenharmony_ci */
16462306a36Sopenharmony_cikuid_t from_vfsuid(struct mnt_idmap *idmap,
16562306a36Sopenharmony_ci		   struct user_namespace *fs_userns, vfsuid_t vfsuid)
16662306a36Sopenharmony_ci{
16762306a36Sopenharmony_ci	uid_t uid;
16862306a36Sopenharmony_ci	struct user_namespace *mnt_userns = idmap->owner;
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	if (no_idmapping(mnt_userns, fs_userns))
17162306a36Sopenharmony_ci		return AS_KUIDT(vfsuid);
17262306a36Sopenharmony_ci	uid = from_kuid(mnt_userns, AS_KUIDT(vfsuid));
17362306a36Sopenharmony_ci	if (uid == (uid_t)-1)
17462306a36Sopenharmony_ci		return INVALID_UID;
17562306a36Sopenharmony_ci	if (initial_idmapping(fs_userns))
17662306a36Sopenharmony_ci		return KUIDT_INIT(uid);
17762306a36Sopenharmony_ci	return make_kuid(fs_userns, uid);
17862306a36Sopenharmony_ci}
17962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(from_vfsuid);
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci/**
18262306a36Sopenharmony_ci * from_vfsgid - map a vfsgid into the filesystem idmapping
18362306a36Sopenharmony_ci * @idmap: the mount's idmapping
18462306a36Sopenharmony_ci * @fs_userns: the filesystem's idmapping
18562306a36Sopenharmony_ci * @vfsgid : vfsgid to be mapped
18662306a36Sopenharmony_ci *
18762306a36Sopenharmony_ci * Map @vfsgid into the filesystem idmapping. This function has to be used in
18862306a36Sopenharmony_ci * order to e.g. write @vfsgid to inode->i_gid.
18962306a36Sopenharmony_ci *
19062306a36Sopenharmony_ci * Return: @vfsgid mapped into the filesystem idmapping
19162306a36Sopenharmony_ci */
19262306a36Sopenharmony_cikgid_t from_vfsgid(struct mnt_idmap *idmap,
19362306a36Sopenharmony_ci		   struct user_namespace *fs_userns, vfsgid_t vfsgid)
19462306a36Sopenharmony_ci{
19562306a36Sopenharmony_ci	gid_t gid;
19662306a36Sopenharmony_ci	struct user_namespace *mnt_userns = idmap->owner;
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	if (no_idmapping(mnt_userns, fs_userns))
19962306a36Sopenharmony_ci		return AS_KGIDT(vfsgid);
20062306a36Sopenharmony_ci	gid = from_kgid(mnt_userns, AS_KGIDT(vfsgid));
20162306a36Sopenharmony_ci	if (gid == (gid_t)-1)
20262306a36Sopenharmony_ci		return INVALID_GID;
20362306a36Sopenharmony_ci	if (initial_idmapping(fs_userns))
20462306a36Sopenharmony_ci		return KGIDT_INIT(gid);
20562306a36Sopenharmony_ci	return make_kgid(fs_userns, gid);
20662306a36Sopenharmony_ci}
20762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(from_vfsgid);
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci#ifdef CONFIG_MULTIUSER
21062306a36Sopenharmony_ci/**
21162306a36Sopenharmony_ci * vfsgid_in_group_p() - check whether a vfsuid matches the caller's groups
21262306a36Sopenharmony_ci * @vfsgid: the mnt gid to match
21362306a36Sopenharmony_ci *
21462306a36Sopenharmony_ci * This function can be used to determine whether @vfsuid matches any of the
21562306a36Sopenharmony_ci * caller's groups.
21662306a36Sopenharmony_ci *
21762306a36Sopenharmony_ci * Return: 1 if vfsuid matches caller's groups, 0 if not.
21862306a36Sopenharmony_ci */
21962306a36Sopenharmony_ciint vfsgid_in_group_p(vfsgid_t vfsgid)
22062306a36Sopenharmony_ci{
22162306a36Sopenharmony_ci	return in_group_p(AS_KGIDT(vfsgid));
22262306a36Sopenharmony_ci}
22362306a36Sopenharmony_ci#else
22462306a36Sopenharmony_ciint vfsgid_in_group_p(vfsgid_t vfsgid)
22562306a36Sopenharmony_ci{
22662306a36Sopenharmony_ci	return 1;
22762306a36Sopenharmony_ci}
22862306a36Sopenharmony_ci#endif
22962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(vfsgid_in_group_p);
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_cistruct mnt_idmap *alloc_mnt_idmap(struct user_namespace *mnt_userns)
23262306a36Sopenharmony_ci{
23362306a36Sopenharmony_ci	struct mnt_idmap *idmap;
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci	idmap = kzalloc(sizeof(struct mnt_idmap), GFP_KERNEL_ACCOUNT);
23662306a36Sopenharmony_ci	if (!idmap)
23762306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci	idmap->owner = get_user_ns(mnt_userns);
24062306a36Sopenharmony_ci	refcount_set(&idmap->count, 1);
24162306a36Sopenharmony_ci	return idmap;
24262306a36Sopenharmony_ci}
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci/**
24562306a36Sopenharmony_ci * mnt_idmap_get - get a reference to an idmapping
24662306a36Sopenharmony_ci * @idmap: the idmap to bump the reference on
24762306a36Sopenharmony_ci *
24862306a36Sopenharmony_ci * If @idmap is not the @nop_mnt_idmap bump the reference count.
24962306a36Sopenharmony_ci *
25062306a36Sopenharmony_ci * Return: @idmap with reference count bumped if @not_mnt_idmap isn't passed.
25162306a36Sopenharmony_ci */
25262306a36Sopenharmony_cistruct mnt_idmap *mnt_idmap_get(struct mnt_idmap *idmap)
25362306a36Sopenharmony_ci{
25462306a36Sopenharmony_ci	if (idmap != &nop_mnt_idmap)
25562306a36Sopenharmony_ci		refcount_inc(&idmap->count);
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	return idmap;
25862306a36Sopenharmony_ci}
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci/**
26162306a36Sopenharmony_ci * mnt_idmap_put - put a reference to an idmapping
26262306a36Sopenharmony_ci * @idmap: the idmap to put the reference on
26362306a36Sopenharmony_ci *
26462306a36Sopenharmony_ci * If this is a non-initial idmapping, put the reference count when a mount is
26562306a36Sopenharmony_ci * released and free it if we're the last user.
26662306a36Sopenharmony_ci */
26762306a36Sopenharmony_civoid mnt_idmap_put(struct mnt_idmap *idmap)
26862306a36Sopenharmony_ci{
26962306a36Sopenharmony_ci	if (idmap != &nop_mnt_idmap && refcount_dec_and_test(&idmap->count)) {
27062306a36Sopenharmony_ci		put_user_ns(idmap->owner);
27162306a36Sopenharmony_ci		kfree(idmap);
27262306a36Sopenharmony_ci	}
27362306a36Sopenharmony_ci}
274