// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2022 Christian Brauner <brauner@kernel.org> */
#include <linux/cred.h>
#include <linux/fs.h>
#include <linux/mnt_idmapping.h>
#include <linux/slab.h>
#include <linux/user_namespace.h>
#include <linux/seq_file.h>
#include "internal.h"
/*
* Outside of this file vfs { g , u } id_t are always created from k { g , u } id_t ,
* never from raw values . These are just internal helpers .
*/
#define VFSUIDT_INIT_RAW(val) (vfsuid_t){ val }
#define VFSGIDT_INIT_RAW(val) (vfsgid_t){ val }
struct mnt_idmap {
struct uid_gid_map uid_map;
struct uid_gid_map gid_map;
refcount_t count;
};
/*
* Carries the initial idmapping of 0 : 0 : 4294967295 which is an identity
* mapping . This means that { g , u } id 0 is mapped to { g , u } id 0 , { g , u } id 1 is
* mapped to { g , u } id 1 , [ . . . ] , { g , u } id 1000 to { g , u } id 1000 , [ . . . ] .
*/
struct mnt_idmap nop_mnt_idmap = {
.count = REFCOUNT_INIT(1 ),
};
EXPORT_SYMBOL_GPL(nop_mnt_idmap);
/*
* Carries the invalid idmapping of a full 0 - 4294967295 { g , u } id range .
* This means that all { g , u } ids are mapped to INVALID_VFS { G , U } ID .
*/
struct mnt_idmap invalid_mnt_idmap = {
.count = REFCOUNT_INIT(1 ),
};
EXPORT_SYMBOL_GPL(invalid_mnt_idmap);
/**
* initial_idmapping - check whether this is the initial mapping
* @ ns : idmapping to check
*
* Check whether this is the initial mapping , mapping 0 to 0 , 1 to 1 ,
* [ . . . ] , 1000 to 1000 [ . . . ] .
*
* Return : true if this is the initial mapping , false if not .
*/
static inline bool initial_idmapping(const struct user_namespace *ns)
{
return ns == &init_user_ns;
}
/**
* make_vfsuid - map a filesystem kuid according to an idmapping
* @ idmap : the mount ' s idmapping
* @ fs_userns : the filesystem ' s idmapping
* @ kuid : kuid to be mapped
*
* Take a @ kuid and remap it from @ fs_userns into @ idmap . Use this
* function when preparing a @ kuid to be reported to userspace .
*
* If initial_idmapping ( ) determines that this is not an idmapped mount
* we can simply return @ kuid unchanged .
* If initial_idmapping ( ) tells us that the filesystem is not mounted with an
* idmapping we know the value of @ kuid won ' t change when calling
* from_kuid ( ) so we can simply retrieve the value via _ _ kuid_val ( )
* directly .
*
* Return : @ kuid mapped according to @ idmap .
* If @ kuid has no mapping in either @ idmap or @ fs_userns INVALID_UID is
* returned .
*/
vfsuid_t make_vfsuid(struct mnt_idmap *idmap,
struct user_namespace *fs_userns,
kuid_t kuid)
{
uid_t uid;
if (idmap == &nop_mnt_idmap)
return VFSUIDT_INIT(kuid);
if (idmap == &invalid_mnt_idmap)
return INVALID_VFSUID;
if (initial_idmapping(fs_userns))
uid = __kuid_val(kuid);
else
uid = from_kuid(fs_userns, kuid);
if (uid == (uid_t)-1 )
return INVALID_VFSUID;
return VFSUIDT_INIT_RAW(map_id_down(&idmap->uid_map, uid));
}
EXPORT_SYMBOL_GPL(make_vfsuid);
/**
* make_vfsgid - map a filesystem kgid according to an idmapping
* @ idmap : the mount ' s idmapping
* @ fs_userns : the filesystem ' s idmapping
* @ kgid : kgid to be mapped
*
* Take a @ kgid and remap it from @ fs_userns into @ idmap . Use this
* function when preparing a @ kgid to be reported to userspace .
*
* If initial_idmapping ( ) determines that this is not an idmapped mount
* we can simply return @ kgid unchanged .
* If initial_idmapping ( ) tells us that the filesystem is not mounted with an
* idmapping we know the value of @ kgid won ' t change when calling
* from_kgid ( ) so we can simply retrieve the value via _ _ kgid_val ( )
* directly .
*
* Return : @ kgid mapped according to @ idmap .
* If @ kgid has no mapping in either @ idmap or @ fs_userns INVALID_GID is
* returned .
*/
vfsgid_t make_vfsgid(struct mnt_idmap *idmap,
struct user_namespace *fs_userns, kgid_t kgid)
{
gid_t gid;
if (idmap == &nop_mnt_idmap)
return VFSGIDT_INIT(kgid);
if (idmap == &invalid_mnt_idmap)
return INVALID_VFSGID;
if (initial_idmapping(fs_userns))
gid = __kgid_val(kgid);
else
gid = from_kgid(fs_userns, kgid);
if (gid == (gid_t)-1 )
return INVALID_VFSGID;
return VFSGIDT_INIT_RAW(map_id_down(&idmap->gid_map, gid));
}
EXPORT_SYMBOL_GPL(make_vfsgid);
/**
* from_vfsuid - map a vfsuid into the filesystem idmapping
* @ idmap : the mount ' s idmapping
* @ fs_userns : the filesystem ' s idmapping
* @ vfsuid : vfsuid to be mapped
*
* Map @ vfsuid into the filesystem idmapping . This function has to be used in
* order to e . g . write @ vfsuid to inode - > i_uid .
*
* Return : @ vfsuid mapped into the filesystem idmapping
*/
kuid_t from_vfsuid(struct mnt_idmap *idmap,
struct user_namespace *fs_userns, vfsuid_t vfsuid)
{
uid_t uid;
if (idmap == &nop_mnt_idmap)
return AS_KUIDT(vfsuid);
if (idmap == &invalid_mnt_idmap)
return INVALID_UID;
uid = map_id_up(&idmap->uid_map, __vfsuid_val(vfsuid));
if (uid == (uid_t)-1 )
return INVALID_UID;
if (initial_idmapping(fs_userns))
return KUIDT_INIT(uid);
return make_kuid(fs_userns, uid);
}
EXPORT_SYMBOL_GPL(from_vfsuid);
/**
* from_vfsgid - map a vfsgid into the filesystem idmapping
* @ idmap : the mount ' s idmapping
* @ fs_userns : the filesystem ' s idmapping
* @ vfsgid : vfsgid to be mapped
*
* Map @ vfsgid into the filesystem idmapping . This function has to be used in
* order to e . g . write @ vfsgid to inode - > i_gid .
*
* Return : @ vfsgid mapped into the filesystem idmapping
*/
kgid_t from_vfsgid(struct mnt_idmap *idmap,
struct user_namespace *fs_userns, vfsgid_t vfsgid)
{
gid_t gid;
if (idmap == &nop_mnt_idmap)
return AS_KGIDT(vfsgid);
if (idmap == &invalid_mnt_idmap)
return INVALID_GID;
gid = map_id_up(&idmap->gid_map, __vfsgid_val(vfsgid));
if (gid == (gid_t)-1 )
return INVALID_GID;
if (initial_idmapping(fs_userns))
return KGIDT_INIT(gid);
return make_kgid(fs_userns, gid);
}
EXPORT_SYMBOL_GPL(from_vfsgid);
#ifdef CONFIG_MULTIUSER
/**
* vfsgid_in_group_p ( ) - check whether a vfsuid matches the caller ' s groups
* @ vfsgid : the mnt gid to match
*
* This function can be used to determine whether @ vfsuid matches any of the
* caller ' s groups .
*
* Return : 1 if vfsuid matches caller ' s groups , 0 if not .
*/
int vfsgid_in_group_p(vfsgid_t vfsgid)
{
return in_group_p(AS_KGIDT(vfsgid));
}
#else
int vfsgid_in_group_p(vfsgid_t vfsgid)
{
return 1 ;
}
#endif
EXPORT_SYMBOL_GPL(vfsgid_in_group_p);
static int copy_mnt_idmap(struct uid_gid_map *map_from,
struct uid_gid_map *map_to)
{
struct uid_gid_extent *forward, *reverse;
u32 nr_extents = READ_ONCE(map_from->nr_extents);
/* Pairs with smp_wmb() when writing the idmapping. */
smp_rmb();
/*
* Don ' t blindly copy @ map_to into @ map_from if nr_extents is
* smaller or equal to UID_GID_MAP_MAX_BASE_EXTENTS . Since we
* read @ nr_extents someone could have written an idmapping and
* then we might end up with inconsistent data . So just don ' t do
* anything at all .
*/
if (nr_extents == 0 )
return -EINVAL;
/*
* Here we know that nr_extents is greater than zero which means
* a map has been written . Since idmappings can ' t be changed
* once they have been written we know that we can safely copy
* from @ map_to into @ map_from .
*/
if (nr_extents <= UID_GID_MAP_MAX_BASE_EXTENTS) {
*map_to = *map_from;
return 0 ;
}
forward = kmemdup_array(map_from->forward, nr_extents,
sizeof (struct uid_gid_extent),
GFP_KERNEL_ACCOUNT);
if (!forward)
return -ENOMEM;
reverse = kmemdup_array(map_from->reverse, nr_extents,
sizeof (struct uid_gid_extent),
GFP_KERNEL_ACCOUNT);
if (!reverse) {
kfree(forward);
return -ENOMEM;
}
/*
* The idmapping isn ' t exposed anywhere so we don ' t need to care
* about ordering between extent pointers and @ nr_extents
* initialization .
*/
map_to->forward = forward;
map_to->reverse = reverse;
map_to->nr_extents = nr_extents;
return 0 ;
}
static void free_mnt_idmap(struct mnt_idmap *idmap)
{
if (idmap->uid_map.nr_extents > UID_GID_MAP_MAX_BASE_EXTENTS) {
kfree(idmap->uid_map.forward);
kfree(idmap->uid_map.reverse);
}
if (idmap->gid_map.nr_extents > UID_GID_MAP_MAX_BASE_EXTENTS) {
kfree(idmap->gid_map.forward);
kfree(idmap->gid_map.reverse);
}
kfree(idmap);
}
struct mnt_idmap *alloc_mnt_idmap(struct user_namespace *mnt_userns)
{
struct mnt_idmap *idmap;
int ret;
idmap = kzalloc(sizeof (struct mnt_idmap), GFP_KERNEL_ACCOUNT);
if (!idmap)
return ERR_PTR(-ENOMEM);
refcount_set(&idmap->count, 1 );
ret = copy_mnt_idmap(&mnt_userns->uid_map, &idmap->uid_map);
if (!ret)
ret = copy_mnt_idmap(&mnt_userns->gid_map, &idmap->gid_map);
if (ret) {
free_mnt_idmap(idmap);
idmap = ERR_PTR(ret);
}
return idmap;
}
/**
* mnt_idmap_get - get a reference to an idmapping
* @ idmap : the idmap to bump the reference on
*
* If @ idmap is not the @ nop_mnt_idmap bump the reference count .
*
* Return : @ idmap with reference count bumped if @ not_mnt_idmap isn ' t passed .
*/
struct mnt_idmap *mnt_idmap_get(struct mnt_idmap *idmap)
{
if (idmap != &nop_mnt_idmap && idmap != &invalid_mnt_idmap)
refcount_inc(&idmap->count);
return idmap;
}
EXPORT_SYMBOL_GPL(mnt_idmap_get);
/**
* mnt_idmap_put - put a reference to an idmapping
* @ idmap : the idmap to put the reference on
*
* If this is a non - initial idmapping , put the reference count when a mount is
* released and free it if we ' re the last user .
*/
void mnt_idmap_put(struct mnt_idmap *idmap)
{
if (idmap != &nop_mnt_idmap && idmap != &invalid_mnt_idmap &&
refcount_dec_and_test(&idmap->count))
free_mnt_idmap(idmap);
}
EXPORT_SYMBOL_GPL(mnt_idmap_put);
int statmount_mnt_idmap(struct mnt_idmap *idmap, struct seq_file *seq, bool uid_map)
{
struct uid_gid_map *map, *map_up;
u32 idx, nr_mappings;
if (!is_valid_mnt_idmap(idmap))
return 0 ;
/*
* Idmappings are shown relative to the caller ' s idmapping .
* This is both the most intuitive and most useful solution .
*/
if (uid_map) {
map = &idmap->uid_map;
map_up = ¤t_user_ns()->uid_map;
} else {
map = &idmap->gid_map;
map_up = ¤t_user_ns()->gid_map;
}
for (idx = 0 , nr_mappings = 0 ; idx < map->nr_extents; idx++) {
uid_t lower;
struct uid_gid_extent *extent;
if (map->nr_extents <= UID_GID_MAP_MAX_BASE_EXTENTS)
extent = &map->extent[idx];
else
extent = &map->forward[idx];
/*
* Verify that the whole range of the mapping can be
* resolved in the caller ' s idmapping . If it cannot be
* resolved skip the mapping .
*/
lower = map_id_range_up(map_up, extent->lower_first, extent->count);
if (lower == (uid_t) -1 )
continue ;
seq_printf(seq, "%u %u %u" , extent->first, lower, extent->count);
seq->count++; /* mappings are separated by \0 */
if (seq_has_overflowed(seq))
return -EAGAIN;
nr_mappings++;
}
return nr_mappings;
}
Messung V0.5 in Prozent C=96 H=99 G=97
¤ Dauer der Verarbeitung: 0.14 Sekunden
(vorverarbeitet am 2026-09-28)
¤
*© Formatika GbR, Deutschland