Skip to content

Commit 5a432de

Browse files
committed
Merge patch series "statmount: allow to retrieve idmappings"
Christian Brauner <brauner@kernel.org> says: This adds the STATMOUNT_MNT_UIDMAP and STATMOUNT_MNT_GIDMAP options. It allows the retrieval of idmappings via statmount(). Currently it isn't possible to figure out what idmappings are applied to an idmapped mount. This information is often crucial. Before statmount() the only realistic options for an interface like this would have been to add it to /proc/<pid>/fdinfo/<nr> or to expose it in /proc/<pid>/mountinfo. Both solution would have been pretty ugly and would've shown information that is of strong interest to some application but not all. statmount() is perfect for this. The idmappings applied to an idmapped mount are shown relative to the caller's user namespace. This is the most useful solution that doesn't risk leaking information or confuse the caller. For example, an idmapped mount might have been created with the following idmappings: mount --bind -o X-mount.idmap="0:10000:1000 2000:2000:1 3000:3000:1" /srv /opt Listing the idmappings through statmount() in the same context shows: mnt_id: 2147485088 mnt_parent_id: 2147484816 fs_type: btrfs mnt_root: /srv mnt_point: /opt mnt_opts: ssd,discard=async,space_cache=v2,subvolid=5,subvol=/ mnt_uidmap[0]: 0 10000 1000 mnt_uidmap[1]: 2000 2000 1 mnt_uidmap[2]: 3000 3000 1 mnt_gidmap[0]: 0 10000 1000 mnt_gidmap[1]: 2000 2000 1 mnt_gidmap[2]: 3000 3000 1 But the idmappings might not always be resolvable in the caller's user namespace. For example: unshare --user --map-root In this case statmount() will skip any mappings that fil to resolve in the caller's idmapping: mnt_id: 2147485087 mnt_parent_id: 2147484016 fs_type: btrfs mnt_root: /srv mnt_point: /opt mnt_opts: ssd,discard=async,space_cache=v2,subvolid=5,subvol=/ The caller can differentiate between a mount not being idmapped and a mount that is idmapped but where all mappings fail to resolve in the caller's idmapping by check for the STATMOUNT_MNT_{G,U}IDMAP flag being raised but the number of mappings in ->mnt_{g,u}idmap_num being zero. Note that statmount() requires that the whole range must be resolvable in the caller's user namespace. If a subrange fails to map it will still list the map as not resolvable. This is a practical compromise to avoid having to find which subranges are resovable and wich aren't. Idmappings are listed as a string array with each mapping separated by zero bytes. This allows to retrieve the idmappings and immediately use them for writing to e.g., /proc/<pid>/{g,u}id_map and it also allow for simple iteration like: if (stmnt->mask & STATMOUNT_MNT_UIDMAP) { const char *idmap = stmnt->str + stmnt->mnt_uidmap; for (size_t idx = 0; idx < stmnt->mnt_uidmap_nr; idx++) { printf("mnt_uidmap[%lu]: %s\n", idx, idmap); idmap += strlen(idmap) + 1; } } * patches from https://lore.kernel.org/r/20250204-work-mnt_idmap-statmount-v2-0-007720f39f2e@kernel.org: samples/vfs: add STATMOUNT_MNT_{G,U}IDMAP samples/vfs: check whether flag was raised statmount: allow to retrieve idmappings uidgid: add map_id_range_up() Link: https://lore.kernel.org/r/20250204-work-mnt_idmap-statmount-v2-0-007720f39f2e@kernel.org Signed-off-by: Christian Brauner <brauner@kernel.org>
2 parents 3129946 + fa204a6 commit 5a432de

9 files changed

Lines changed: 187 additions & 18 deletions

File tree

fs/internal.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,3 +338,4 @@ static inline bool path_mounted(const struct path *path)
338338
return path->mnt->mnt_root == path->dentry;
339339
}
340340
void file_f_owner_release(struct file *file);
341+
int statmount_mnt_idmap(struct mnt_idmap *idmap, struct seq_file *seq, bool uid_map);

fs/mnt_idmapping.c

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <linux/mnt_idmapping.h>
77
#include <linux/slab.h>
88
#include <linux/user_namespace.h>
9+
#include <linux/seq_file.h>
910

1011
#include "internal.h"
1112

@@ -334,3 +335,53 @@ void mnt_idmap_put(struct mnt_idmap *idmap)
334335
free_mnt_idmap(idmap);
335336
}
336337
EXPORT_SYMBOL_GPL(mnt_idmap_put);
338+
339+
int statmount_mnt_idmap(struct mnt_idmap *idmap, struct seq_file *seq, bool uid_map)
340+
{
341+
struct uid_gid_map *map, *map_up;
342+
u32 idx, nr_mappings;
343+
344+
if (!is_valid_mnt_idmap(idmap))
345+
return 0;
346+
347+
/*
348+
* Idmappings are shown relative to the caller's idmapping.
349+
* This is both the most intuitive and most useful solution.
350+
*/
351+
if (uid_map) {
352+
map = &idmap->uid_map;
353+
map_up = &current_user_ns()->uid_map;
354+
} else {
355+
map = &idmap->gid_map;
356+
map_up = &current_user_ns()->gid_map;
357+
}
358+
359+
for (idx = 0, nr_mappings = 0; idx < map->nr_extents; idx++) {
360+
uid_t lower;
361+
struct uid_gid_extent *extent;
362+
363+
if (map->nr_extents <= UID_GID_MAP_MAX_BASE_EXTENTS)
364+
extent = &map->extent[idx];
365+
else
366+
extent = &map->forward[idx];
367+
368+
/*
369+
* Verify that the whole range of the mapping can be
370+
* resolved in the caller's idmapping. If it cannot be
371+
* resolved skip the mapping.
372+
*/
373+
lower = map_id_range_up(map_up, extent->lower_first, extent->count);
374+
if (lower == (uid_t) -1)
375+
continue;
376+
377+
seq_printf(seq, "%u %u %u", extent->first, lower, extent->count);
378+
379+
seq->count++; /* mappings are separated by \0 */
380+
if (seq_has_overflowed(seq))
381+
return -EAGAIN;
382+
383+
nr_mappings++;
384+
}
385+
386+
return nr_mappings;
387+
}

fs/namespace.c

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5008,6 +5008,7 @@ struct kstatmount {
50085008
struct statmount __user *buf;
50095009
size_t bufsize;
50105010
struct vfsmount *mnt;
5011+
struct mnt_idmap *idmap;
50115012
u64 mask;
50125013
struct path root;
50135014
struct statmount sm;
@@ -5278,6 +5279,46 @@ static int statmount_opt_sec_array(struct kstatmount *s, struct seq_file *seq)
52785279
return 0;
52795280
}
52805281

5282+
static inline int statmount_mnt_uidmap(struct kstatmount *s, struct seq_file *seq)
5283+
{
5284+
int ret;
5285+
5286+
ret = statmount_mnt_idmap(s->idmap, seq, true);
5287+
if (ret < 0)
5288+
return ret;
5289+
5290+
s->sm.mnt_uidmap_num = ret;
5291+
/*
5292+
* Always raise STATMOUNT_MNT_UIDMAP even if there are no valid
5293+
* mappings. This allows userspace to distinguish between a
5294+
* non-idmapped mount and an idmapped mount where none of the
5295+
* individual mappings are valid in the caller's idmapping.
5296+
*/
5297+
if (is_valid_mnt_idmap(s->idmap))
5298+
s->sm.mask |= STATMOUNT_MNT_UIDMAP;
5299+
return 0;
5300+
}
5301+
5302+
static inline int statmount_mnt_gidmap(struct kstatmount *s, struct seq_file *seq)
5303+
{
5304+
int ret;
5305+
5306+
ret = statmount_mnt_idmap(s->idmap, seq, false);
5307+
if (ret < 0)
5308+
return ret;
5309+
5310+
s->sm.mnt_gidmap_num = ret;
5311+
/*
5312+
* Always raise STATMOUNT_MNT_GIDMAP even if there are no valid
5313+
* mappings. This allows userspace to distinguish between a
5314+
* non-idmapped mount and an idmapped mount where none of the
5315+
* individual mappings are valid in the caller's idmapping.
5316+
*/
5317+
if (is_valid_mnt_idmap(s->idmap))
5318+
s->sm.mask |= STATMOUNT_MNT_GIDMAP;
5319+
return 0;
5320+
}
5321+
52815322
static int statmount_string(struct kstatmount *s, u64 flag)
52825323
{
52835324
int ret = 0;
@@ -5319,6 +5360,14 @@ static int statmount_string(struct kstatmount *s, u64 flag)
53195360
sm->sb_source = start;
53205361
ret = statmount_sb_source(s, seq);
53215362
break;
5363+
case STATMOUNT_MNT_UIDMAP:
5364+
sm->mnt_uidmap = start;
5365+
ret = statmount_mnt_uidmap(s, seq);
5366+
break;
5367+
case STATMOUNT_MNT_GIDMAP:
5368+
sm->mnt_gidmap = start;
5369+
ret = statmount_mnt_gidmap(s, seq);
5370+
break;
53225371
default:
53235372
WARN_ON_ONCE(true);
53245373
return -EINVAL;
@@ -5443,6 +5492,7 @@ static int do_statmount(struct kstatmount *s, u64 mnt_id, u64 mnt_ns_id,
54435492
return err;
54445493

54455494
s->root = root;
5495+
s->idmap = mnt_idmap(s->mnt);
54465496
if (s->mask & STATMOUNT_SB_BASIC)
54475497
statmount_sb_basic(s);
54485498

@@ -5476,6 +5526,12 @@ static int do_statmount(struct kstatmount *s, u64 mnt_id, u64 mnt_ns_id,
54765526
if (!err && s->mask & STATMOUNT_SB_SOURCE)
54775527
err = statmount_string(s, STATMOUNT_SB_SOURCE);
54785528

5529+
if (!err && s->mask & STATMOUNT_MNT_UIDMAP)
5530+
err = statmount_string(s, STATMOUNT_MNT_UIDMAP);
5531+
5532+
if (!err && s->mask & STATMOUNT_MNT_GIDMAP)
5533+
err = statmount_string(s, STATMOUNT_MNT_GIDMAP);
5534+
54795535
if (!err && s->mask & STATMOUNT_MNT_NS_ID)
54805536
statmount_mnt_ns_id(s, ns);
54815537

@@ -5499,7 +5555,8 @@ static inline bool retry_statmount(const long ret, size_t *seq_size)
54995555
#define STATMOUNT_STRING_REQ (STATMOUNT_MNT_ROOT | STATMOUNT_MNT_POINT | \
55005556
STATMOUNT_FS_TYPE | STATMOUNT_MNT_OPTS | \
55015557
STATMOUNT_FS_SUBTYPE | STATMOUNT_SB_SOURCE | \
5502-
STATMOUNT_OPT_ARRAY | STATMOUNT_OPT_SEC_ARRAY)
5558+
STATMOUNT_OPT_ARRAY | STATMOUNT_OPT_SEC_ARRAY | \
5559+
STATMOUNT_MNT_UIDMAP | STATMOUNT_MNT_GIDMAP)
55035560

55045561
static int prepare_kstatmount(struct kstatmount *ks, struct mnt_id_req *kreq,
55055562
struct statmount __user *buf, size_t bufsize,

include/linux/mnt_idmapping.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ static_assert(sizeof(vfsgid_t) == sizeof(kgid_t));
2525
static_assert(offsetof(vfsuid_t, val) == offsetof(kuid_t, val));
2626
static_assert(offsetof(vfsgid_t, val) == offsetof(kgid_t, val));
2727

28+
static inline bool is_valid_mnt_idmap(const struct mnt_idmap *idmap)
29+
{
30+
return idmap != &nop_mnt_idmap && idmap != &invalid_mnt_idmap;
31+
}
32+
2833
#ifdef CONFIG_MULTIUSER
2934
static inline uid_t __vfsuid_val(vfsuid_t uid)
3035
{

include/linux/uidgid.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ static inline bool kgid_has_mapping(struct user_namespace *ns, kgid_t gid)
132132

133133
u32 map_id_down(struct uid_gid_map *map, u32 id);
134134
u32 map_id_up(struct uid_gid_map *map, u32 id);
135+
u32 map_id_range_up(struct uid_gid_map *map, u32 id, u32 count);
135136

136137
#else
137138

@@ -186,6 +187,11 @@ static inline u32 map_id_down(struct uid_gid_map *map, u32 id)
186187
return id;
187188
}
188189

190+
static inline u32 map_id_range_up(struct uid_gid_map *map, u32 id, u32 count)
191+
{
192+
return id;
193+
}
194+
189195
static inline u32 map_id_up(struct uid_gid_map *map, u32 id)
190196
{
191197
return id;

include/uapi/linux/mount.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,11 @@ struct statmount {
179179
__u32 opt_array; /* [str] Array of nul terminated fs options */
180180
__u32 opt_sec_num; /* Number of security options */
181181
__u32 opt_sec_array; /* [str] Array of nul terminated security options */
182-
__u64 __spare2[46];
182+
__u32 mnt_uidmap_num; /* Number of uid mappings */
183+
__u32 mnt_uidmap; /* [str] Array of uid mappings (as seen from callers namespace) */
184+
__u32 mnt_gidmap_num; /* Number of gid mappings */
185+
__u32 mnt_gidmap; /* [str] Array of gid mappings (as seen from callers namespace) */
186+
__u64 __spare2[44];
183187
char str[]; /* Variable size part containing strings */
184188
};
185189

@@ -217,6 +221,8 @@ struct mnt_id_req {
217221
#define STATMOUNT_SB_SOURCE 0x00000200U /* Want/got sb_source */
218222
#define STATMOUNT_OPT_ARRAY 0x00000400U /* Want/got opt_... */
219223
#define STATMOUNT_OPT_SEC_ARRAY 0x00000800U /* Want/got opt_sec... */
224+
#define STATMOUNT_MNT_UIDMAP 0x00001000U /* Want/got uidmap... */
225+
#define STATMOUNT_MNT_GIDMAP 0x00002000U /* Want/got gidmap... */
220226

221227
/*
222228
* Special @mnt_id values that can be passed to listmount

kernel/user_namespace.c

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ EXPORT_SYMBOL(__put_user_ns);
238238
struct idmap_key {
239239
bool map_up; /* true -> id from kid; false -> kid from id */
240240
u32 id; /* id to find */
241-
u32 count; /* == 0 unless used with map_id_range_down() */
241+
u32 count;
242242
};
243243

244244
/*
@@ -343,16 +343,19 @@ u32 map_id_down(struct uid_gid_map *map, u32 id)
343343
* UID_GID_MAP_MAX_BASE_EXTENTS.
344344
*/
345345
static struct uid_gid_extent *
346-
map_id_up_base(unsigned extents, struct uid_gid_map *map, u32 id)
346+
map_id_range_up_base(unsigned extents, struct uid_gid_map *map, u32 id, u32 count)
347347
{
348348
unsigned idx;
349-
u32 first, last;
349+
u32 first, last, id2;
350+
351+
id2 = id + count - 1;
350352

351353
/* Find the matching extent */
352354
for (idx = 0; idx < extents; idx++) {
353355
first = map->extent[idx].lower_first;
354356
last = first + map->extent[idx].count - 1;
355-
if (id >= first && id <= last)
357+
if (id >= first && id <= last &&
358+
(id2 >= first && id2 <= last))
356359
return &map->extent[idx];
357360
}
358361
return NULL;
@@ -363,28 +366,28 @@ map_id_up_base(unsigned extents, struct uid_gid_map *map, u32 id)
363366
* Can only be called if number of mappings exceeds UID_GID_MAP_MAX_BASE_EXTENTS.
364367
*/
365368
static struct uid_gid_extent *
366-
map_id_up_max(unsigned extents, struct uid_gid_map *map, u32 id)
369+
map_id_range_up_max(unsigned extents, struct uid_gid_map *map, u32 id, u32 count)
367370
{
368371
struct idmap_key key;
369372

370373
key.map_up = true;
371-
key.count = 1;
374+
key.count = count;
372375
key.id = id;
373376

374377
return bsearch(&key, map->reverse, extents,
375378
sizeof(struct uid_gid_extent), cmp_map_id);
376379
}
377380

378-
u32 map_id_up(struct uid_gid_map *map, u32 id)
381+
u32 map_id_range_up(struct uid_gid_map *map, u32 id, u32 count)
379382
{
380383
struct uid_gid_extent *extent;
381384
unsigned extents = map->nr_extents;
382385
smp_rmb();
383386

384387
if (extents <= UID_GID_MAP_MAX_BASE_EXTENTS)
385-
extent = map_id_up_base(extents, map, id);
388+
extent = map_id_range_up_base(extents, map, id, count);
386389
else
387-
extent = map_id_up_max(extents, map, id);
390+
extent = map_id_range_up_max(extents, map, id, count);
388391

389392
/* Map the id or note failure */
390393
if (extent)
@@ -395,6 +398,11 @@ u32 map_id_up(struct uid_gid_map *map, u32 id)
395398
return id;
396399
}
397400

401+
u32 map_id_up(struct uid_gid_map *map, u32 id)
402+
{
403+
return map_id_range_up(map, id, 1);
404+
}
405+
398406
/**
399407
* make_kuid - Map a user-namespace uid pair into a kuid.
400408
* @ns: User namespace that the uid is in

samples/vfs/samples-vfs.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ struct statmount {
4242
__u32 opt_array; /* [str] Array of nul terminated fs options */
4343
__u32 opt_sec_num; /* Number of security options */
4444
__u32 opt_sec_array; /* [str] Array of nul terminated security options */
45-
__u64 __spare2[46];
45+
__u32 mnt_uidmap_num; /* Number of uid mappings */
46+
__u32 mnt_uidmap; /* [str] Array of uid mappings */
47+
__u32 mnt_gidmap_num; /* Number of gid mappings */
48+
__u32 mnt_gidmap; /* [str] Array of gid mappings */
49+
__u64 __spare2[44];
4650
char str[]; /* Variable size part containing strings */
4751
};
4852

@@ -158,6 +162,14 @@ struct mnt_ns_info {
158162
#define STATX_MNT_ID_UNIQUE 0x00004000U /* Want/got extended stx_mount_id */
159163
#endif
160164

165+
#ifndef STATMOUNT_MNT_UIDMAP
166+
#define STATMOUNT_MNT_UIDMAP 0x00002000U /* Want/got uidmap... */
167+
#endif
168+
169+
#ifndef STATMOUNT_MNT_GIDMAP
170+
#define STATMOUNT_MNT_GIDMAP 0x00004000U /* Want/got gidmap... */
171+
#endif
172+
161173
#ifndef MOUNT_ATTR_RDONLY
162174
#define MOUNT_ATTR_RDONLY 0x00000001 /* Mount read-only */
163175
#endif

samples/vfs/test-list-all-mounts.c

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,20 +128,43 @@ int main(int argc, char *argv[])
128128
STATMOUNT_MNT_POINT |
129129
STATMOUNT_MNT_NS_ID |
130130
STATMOUNT_MNT_OPTS |
131-
STATMOUNT_FS_TYPE, 0);
131+
STATMOUNT_FS_TYPE |
132+
STATMOUNT_MNT_UIDMAP |
133+
STATMOUNT_MNT_GIDMAP, 0);
132134
if (!stmnt) {
133135
printf("Failed to statmount(%" PRIu64 ") in mount namespace(%" PRIu64 ")\n",
134136
(uint64_t)last_mnt_id, (uint64_t)info.mnt_ns_id);
135137
continue;
136138
}
137139

138-
printf("mnt_id:\t\t%" PRIu64 "\nmnt_parent_id:\t%" PRIu64 "\nfs_type:\t%s\nmnt_root:\t%s\nmnt_point:\t%s\nmnt_opts:\t%s\n\n",
140+
printf("mnt_id:\t\t%" PRIu64 "\nmnt_parent_id:\t%" PRIu64 "\nfs_type:\t%s\nmnt_root:\t%s\nmnt_point:\t%s\nmnt_opts:\t%s\n",
139141
(uint64_t)stmnt->mnt_id,
140142
(uint64_t)stmnt->mnt_parent_id,
141-
stmnt->str + stmnt->fs_type,
142-
stmnt->str + stmnt->mnt_root,
143-
stmnt->str + stmnt->mnt_point,
144-
stmnt->str + stmnt->mnt_opts);
143+
(stmnt->mask & STATMOUNT_FS_TYPE) ? stmnt->str + stmnt->fs_type : "",
144+
(stmnt->mask & STATMOUNT_MNT_ROOT) ? stmnt->str + stmnt->mnt_root : "",
145+
(stmnt->mask & STATMOUNT_MNT_POINT) ? stmnt->str + stmnt->mnt_point : "",
146+
(stmnt->mask & STATMOUNT_MNT_OPTS) ? stmnt->str + stmnt->mnt_opts : "");
147+
148+
if (stmnt->mask & STATMOUNT_MNT_UIDMAP) {
149+
const char *idmap = stmnt->str + stmnt->mnt_uidmap;
150+
151+
for (size_t idx = 0; idx < stmnt->mnt_uidmap_num; idx++) {
152+
printf("mnt_uidmap[%lu]:\t%s\n", idx, idmap);
153+
idmap += strlen(idmap) + 1;
154+
}
155+
}
156+
157+
if (stmnt->mask & STATMOUNT_MNT_GIDMAP) {
158+
const char *idmap = stmnt->str + stmnt->mnt_gidmap;
159+
160+
for (size_t idx = 0; idx < stmnt->mnt_gidmap_num; idx++) {
161+
printf("mnt_gidmap[%lu]:\t%s\n", idx, idmap);
162+
idmap += strlen(idmap) + 1;
163+
}
164+
}
165+
166+
printf("\n");
167+
145168
free(stmnt);
146169
}
147170
}

0 commit comments

Comments
 (0)