Linux Audio

Check our new training course

Loading...
Note: File does not exist in v4.10.11.
  1// SPDX-License-Identifier: GPL-2.0
  2/* Copyright (c) 2022 Christian Brauner <brauner@kernel.org> */
  3
  4#include <linux/cred.h>
  5#include <linux/fs.h>
  6#include <linux/mnt_idmapping.h>
  7#include <linux/slab.h>
  8#include <linux/user_namespace.h>
  9
 10#include "internal.h"
 11
 12/*
 13 * Outside of this file vfs{g,u}id_t are always created from k{g,u}id_t,
 14 * never from raw values. These are just internal helpers.
 15 */
 16#define VFSUIDT_INIT_RAW(val) (vfsuid_t){ val }
 17#define VFSGIDT_INIT_RAW(val) (vfsgid_t){ val }
 18
 19struct mnt_idmap {
 20	struct uid_gid_map uid_map;
 21	struct uid_gid_map gid_map;
 22	refcount_t count;
 23};
 24
 25/*
 26 * Carries the initial idmapping of 0:0:4294967295 which is an identity
 27 * mapping. This means that {g,u}id 0 is mapped to {g,u}id 0, {g,u}id 1 is
 28 * mapped to {g,u}id 1, [...], {g,u}id 1000 to {g,u}id 1000, [...].
 29 */
 30struct mnt_idmap nop_mnt_idmap = {
 31	.count	= REFCOUNT_INIT(1),
 32};
 33EXPORT_SYMBOL_GPL(nop_mnt_idmap);
 34
 35/*
 36 * Carries the invalid idmapping of a full 0-4294967295 {g,u}id range.
 37 * This means that all {g,u}ids are mapped to INVALID_VFS{G,U}ID.
 38 */
 39struct mnt_idmap invalid_mnt_idmap = {
 40	.count	= REFCOUNT_INIT(1),
 41};
 42EXPORT_SYMBOL_GPL(invalid_mnt_idmap);
 43
 44/**
 45 * initial_idmapping - check whether this is the initial mapping
 46 * @ns: idmapping to check
 47 *
 48 * Check whether this is the initial mapping, mapping 0 to 0, 1 to 1,
 49 * [...], 1000 to 1000 [...].
 50 *
 51 * Return: true if this is the initial mapping, false if not.
 52 */
 53static inline bool initial_idmapping(const struct user_namespace *ns)
 54{
 55	return ns == &init_user_ns;
 56}
 57
 58/**
 59 * make_vfsuid - map a filesystem kuid according to an idmapping
 60 * @idmap: the mount's idmapping
 61 * @fs_userns: the filesystem's idmapping
 62 * @kuid : kuid to be mapped
 63 *
 64 * Take a @kuid and remap it from @fs_userns into @idmap. Use this
 65 * function when preparing a @kuid to be reported to userspace.
 66 *
 67 * If initial_idmapping() determines that this is not an idmapped mount
 68 * we can simply return @kuid unchanged.
 69 * If initial_idmapping() tells us that the filesystem is not mounted with an
 70 * idmapping we know the value of @kuid won't change when calling
 71 * from_kuid() so we can simply retrieve the value via __kuid_val()
 72 * directly.
 73 *
 74 * Return: @kuid mapped according to @idmap.
 75 * If @kuid has no mapping in either @idmap or @fs_userns INVALID_UID is
 76 * returned.
 77 */
 78
 79vfsuid_t make_vfsuid(struct mnt_idmap *idmap,
 80		     struct user_namespace *fs_userns,
 81		     kuid_t kuid)
 82{
 83	uid_t uid;
 84
 85	if (idmap == &nop_mnt_idmap)
 86		return VFSUIDT_INIT(kuid);
 87	if (idmap == &invalid_mnt_idmap)
 88		return INVALID_VFSUID;
 89	if (initial_idmapping(fs_userns))
 90		uid = __kuid_val(kuid);
 91	else
 92		uid = from_kuid(fs_userns, kuid);
 93	if (uid == (uid_t)-1)
 94		return INVALID_VFSUID;
 95	return VFSUIDT_INIT_RAW(map_id_down(&idmap->uid_map, uid));
 96}
 97EXPORT_SYMBOL_GPL(make_vfsuid);
 98
 99/**
100 * make_vfsgid - map a filesystem kgid according to an idmapping
101 * @idmap: the mount's idmapping
102 * @fs_userns: the filesystem's idmapping
103 * @kgid : kgid to be mapped
104 *
105 * Take a @kgid and remap it from @fs_userns into @idmap. Use this
106 * function when preparing a @kgid to be reported to userspace.
107 *
108 * If initial_idmapping() determines that this is not an idmapped mount
109 * we can simply return @kgid unchanged.
110 * If initial_idmapping() tells us that the filesystem is not mounted with an
111 * idmapping we know the value of @kgid won't change when calling
112 * from_kgid() so we can simply retrieve the value via __kgid_val()
113 * directly.
114 *
115 * Return: @kgid mapped according to @idmap.
116 * If @kgid has no mapping in either @idmap or @fs_userns INVALID_GID is
117 * returned.
118 */
119vfsgid_t make_vfsgid(struct mnt_idmap *idmap,
120		     struct user_namespace *fs_userns, kgid_t kgid)
121{
122	gid_t gid;
123
124	if (idmap == &nop_mnt_idmap)
125		return VFSGIDT_INIT(kgid);
126	if (idmap == &invalid_mnt_idmap)
127		return INVALID_VFSGID;
128	if (initial_idmapping(fs_userns))
129		gid = __kgid_val(kgid);
130	else
131		gid = from_kgid(fs_userns, kgid);
132	if (gid == (gid_t)-1)
133		return INVALID_VFSGID;
134	return VFSGIDT_INIT_RAW(map_id_down(&idmap->gid_map, gid));
135}
136EXPORT_SYMBOL_GPL(make_vfsgid);
137
138/**
139 * from_vfsuid - map a vfsuid into the filesystem idmapping
140 * @idmap: the mount's idmapping
141 * @fs_userns: the filesystem's idmapping
142 * @vfsuid : vfsuid to be mapped
143 *
144 * Map @vfsuid into the filesystem idmapping. This function has to be used in
145 * order to e.g. write @vfsuid to inode->i_uid.
146 *
147 * Return: @vfsuid mapped into the filesystem idmapping
148 */
149kuid_t from_vfsuid(struct mnt_idmap *idmap,
150		   struct user_namespace *fs_userns, vfsuid_t vfsuid)
151{
152	uid_t uid;
153
154	if (idmap == &nop_mnt_idmap)
155		return AS_KUIDT(vfsuid);
156	if (idmap == &invalid_mnt_idmap)
157		return INVALID_UID;
158	uid = map_id_up(&idmap->uid_map, __vfsuid_val(vfsuid));
159	if (uid == (uid_t)-1)
160		return INVALID_UID;
161	if (initial_idmapping(fs_userns))
162		return KUIDT_INIT(uid);
163	return make_kuid(fs_userns, uid);
164}
165EXPORT_SYMBOL_GPL(from_vfsuid);
166
167/**
168 * from_vfsgid - map a vfsgid into the filesystem idmapping
169 * @idmap: the mount's idmapping
170 * @fs_userns: the filesystem's idmapping
171 * @vfsgid : vfsgid to be mapped
172 *
173 * Map @vfsgid into the filesystem idmapping. This function has to be used in
174 * order to e.g. write @vfsgid to inode->i_gid.
175 *
176 * Return: @vfsgid mapped into the filesystem idmapping
177 */
178kgid_t from_vfsgid(struct mnt_idmap *idmap,
179		   struct user_namespace *fs_userns, vfsgid_t vfsgid)
180{
181	gid_t gid;
182
183	if (idmap == &nop_mnt_idmap)
184		return AS_KGIDT(vfsgid);
185	if (idmap == &invalid_mnt_idmap)
186		return INVALID_GID;
187	gid = map_id_up(&idmap->gid_map, __vfsgid_val(vfsgid));
188	if (gid == (gid_t)-1)
189		return INVALID_GID;
190	if (initial_idmapping(fs_userns))
191		return KGIDT_INIT(gid);
192	return make_kgid(fs_userns, gid);
193}
194EXPORT_SYMBOL_GPL(from_vfsgid);
195
196#ifdef CONFIG_MULTIUSER
197/**
198 * vfsgid_in_group_p() - check whether a vfsuid matches the caller's groups
199 * @vfsgid: the mnt gid to match
200 *
201 * This function can be used to determine whether @vfsuid matches any of the
202 * caller's groups.
203 *
204 * Return: 1 if vfsuid matches caller's groups, 0 if not.
205 */
206int vfsgid_in_group_p(vfsgid_t vfsgid)
207{
208	return in_group_p(AS_KGIDT(vfsgid));
209}
210#else
211int vfsgid_in_group_p(vfsgid_t vfsgid)
212{
213	return 1;
214}
215#endif
216EXPORT_SYMBOL_GPL(vfsgid_in_group_p);
217
218static int copy_mnt_idmap(struct uid_gid_map *map_from,
219			  struct uid_gid_map *map_to)
220{
221	struct uid_gid_extent *forward, *reverse;
222	u32 nr_extents = READ_ONCE(map_from->nr_extents);
223	/* Pairs with smp_wmb() when writing the idmapping. */
224	smp_rmb();
225
226	/*
227	 * Don't blindly copy @map_to into @map_from if nr_extents is
228	 * smaller or equal to UID_GID_MAP_MAX_BASE_EXTENTS. Since we
229	 * read @nr_extents someone could have written an idmapping and
230	 * then we might end up with inconsistent data. So just don't do
231	 * anything at all.
232	 */
233	if (nr_extents == 0)
234		return -EINVAL;
235
236	/*
237	 * Here we know that nr_extents is greater than zero which means
238	 * a map has been written. Since idmappings can't be changed
239	 * once they have been written we know that we can safely copy
240	 * from @map_to into @map_from.
241	 */
242
243	if (nr_extents <= UID_GID_MAP_MAX_BASE_EXTENTS) {
244		*map_to = *map_from;
245		return 0;
246	}
247
248	forward = kmemdup_array(map_from->forward, nr_extents,
249				sizeof(struct uid_gid_extent),
250				GFP_KERNEL_ACCOUNT);
251	if (!forward)
252		return -ENOMEM;
253
254	reverse = kmemdup_array(map_from->reverse, nr_extents,
255				sizeof(struct uid_gid_extent),
256				GFP_KERNEL_ACCOUNT);
257	if (!reverse) {
258		kfree(forward);
259		return -ENOMEM;
260	}
261
262	/*
263	 * The idmapping isn't exposed anywhere so we don't need to care
264	 * about ordering between extent pointers and @nr_extents
265	 * initialization.
266	 */
267	map_to->forward = forward;
268	map_to->reverse = reverse;
269	map_to->nr_extents = nr_extents;
270	return 0;
271}
272
273static void free_mnt_idmap(struct mnt_idmap *idmap)
274{
275	if (idmap->uid_map.nr_extents > UID_GID_MAP_MAX_BASE_EXTENTS) {
276		kfree(idmap->uid_map.forward);
277		kfree(idmap->uid_map.reverse);
278	}
279	if (idmap->gid_map.nr_extents > UID_GID_MAP_MAX_BASE_EXTENTS) {
280		kfree(idmap->gid_map.forward);
281		kfree(idmap->gid_map.reverse);
282	}
283	kfree(idmap);
284}
285
286struct mnt_idmap *alloc_mnt_idmap(struct user_namespace *mnt_userns)
287{
288	struct mnt_idmap *idmap;
289	int ret;
290
291	idmap = kzalloc(sizeof(struct mnt_idmap), GFP_KERNEL_ACCOUNT);
292	if (!idmap)
293		return ERR_PTR(-ENOMEM);
294
295	refcount_set(&idmap->count, 1);
296	ret = copy_mnt_idmap(&mnt_userns->uid_map, &idmap->uid_map);
297	if (!ret)
298		ret = copy_mnt_idmap(&mnt_userns->gid_map, &idmap->gid_map);
299	if (ret) {
300		free_mnt_idmap(idmap);
301		idmap = ERR_PTR(ret);
302	}
303	return idmap;
304}
305
306/**
307 * mnt_idmap_get - get a reference to an idmapping
308 * @idmap: the idmap to bump the reference on
309 *
310 * If @idmap is not the @nop_mnt_idmap bump the reference count.
311 *
312 * Return: @idmap with reference count bumped if @not_mnt_idmap isn't passed.
313 */
314struct mnt_idmap *mnt_idmap_get(struct mnt_idmap *idmap)
315{
316	if (idmap != &nop_mnt_idmap && idmap != &invalid_mnt_idmap)
317		refcount_inc(&idmap->count);
318
319	return idmap;
320}
321EXPORT_SYMBOL_GPL(mnt_idmap_get);
322
323/**
324 * mnt_idmap_put - put a reference to an idmapping
325 * @idmap: the idmap to put the reference on
326 *
327 * If this is a non-initial idmapping, put the reference count when a mount is
328 * released and free it if we're the last user.
329 */
330void mnt_idmap_put(struct mnt_idmap *idmap)
331{
332	if (idmap != &nop_mnt_idmap && idmap != &invalid_mnt_idmap &&
333	    refcount_dec_and_test(&idmap->count))
334		free_mnt_idmap(idmap);
335}
336EXPORT_SYMBOL_GPL(mnt_idmap_put);