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