ext2fs_xattr.c revision 1.2 1 /* $NetBSD: ext2fs_xattr.c,v 1.2 2016/08/13 07:40:10 christos Exp $ */
2
3 /*-
4 * Copyright (c) 2016 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Jaromir Dolecek.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 #include <sys/cdefs.h>
33 __KERNEL_RCSID(0, "$NetBSD: ext2fs_xattr.c,v 1.2 2016/08/13 07:40:10 christos Exp $");
34
35 #include <sys/param.h>
36 #include <sys/systm.h>
37 #include <sys/mount.h>
38 #include <sys/proc.h>
39 #include <sys/file.h>
40 #include <sys/buf.h>
41 #include <sys/vnode.h>
42 #include <sys/kernel.h>
43 #include <sys/kmem.h>
44 #include <sys/trace.h>
45 #include <sys/resourcevar.h>
46 #include <sys/kauth.h>
47 #include <sys/extattr.h>
48
49 #include <ufs/ufs/inode.h>
50 #include <ufs/ufs/ufsmount.h>
51 #include <ufs/ufs/ufs_extern.h>
52
53 #include <ufs/ext2fs/ext2fs.h>
54 #include <ufs/ext2fs/ext2fs_extern.h>
55 #include <ufs/ext2fs/ext2fs_xattr.h>
56
57 static const char * const xattr_prefix_index[] = {
58 "",
59 "user.",
60 "system.posix_acl_access",
61 "system.posix_acl_default",
62 "trusted.",
63 "", /* unused */
64 "security",
65 "system.",
66 "system.richacl",
67 "c",
68 };
69
70 static int
71 ext2fs_find_xattr(struct ext2fs_xattr_entry *e, uint8_t *start, uint8_t *end, int attrnamespace, struct uio *uio, size_t *size, uint8_t name_index, const char *name)
72 {
73 uint8_t *value;
74 int error;
75 size_t value_offs, value_len, len, old_len;
76
77 /*
78 * Individual entries follow the header. Each is aligned on 4-byte
79 * boundary.
80 */
81 for(; !EXT2FS_XATTR_IS_LAST_ENTRY(e, end); e = EXT2FS_XATTR_NEXT(e)) {
82 /*
83 * Only EXT2FS_XATTR_PREFIX_USER is USER, anything else
84 * is considered SYSTEM.
85 */
86 if ((attrnamespace == EXTATTR_NAMESPACE_USER && e->e_name_index != EXT2FS_XATTR_PREFIX_USER) ||
87 (attrnamespace == EXTATTR_NAMESPACE_SYSTEM && e->e_name_index == EXT2FS_XATTR_PREFIX_USER)) {
88 continue;
89 }
90
91 if (e->e_name_index != name_index ||
92 e->e_name_len != strlen(name) ||
93 strncmp(e->e_name, name, e->e_name_len) != 0)
94 continue;
95
96 value_offs = fs2h32(e->e_value_offs);
97 value_len = fs2h32(e->e_value_size);
98 value = &start[value_offs];
99
100 /* make sure the value offset are sane */
101 if (&value[value_len] > end)
102 return EINVAL;
103
104 if (uio != NULL) {
105 /*
106 * Figure out maximum to transfer -- use buffer size and
107 * local data limit.
108 */
109 len = MIN(uio->uio_resid, value_len);
110 old_len = uio->uio_resid;
111 uio->uio_resid = len;
112
113 uio->uio_resid = old_len - (len - uio->uio_resid);
114
115 error = uiomove(value, value_len, uio);
116 if (error)
117 return error;
118 }
119
120 /* full data size */
121 *size += value_len;
122
123 goto found;
124 }
125
126 /* requested attribute not found */
127 return ENODATA;
128
129 found:
130 return 0;
131 }
132
133 static int
134 ext2fs_get_inode_xattr(struct inode *ip, int attrnamespace, struct uio *uio, size_t *size, uint8_t name_index, const char *name)
135 {
136 struct ext2fs_dinode *di = ip->i_din.e2fs_din;
137 struct ext2fs_xattr_ibody_header *h;
138 uint8_t *start, *end;
139
140 start = &((uint8_t *)di)[EXT2_REV0_DINODE_SIZE + di->e2di_extra_isize];
141 h = (struct ext2fs_xattr_ibody_header *)start;
142 end = &((uint8_t *)di)[EXT2_DINODE_SIZE(ip->i_e2fs)];
143
144 if (end <= start || fs2h32(h->h_magic) != EXT2FS_XATTR_MAGIC)
145 return ENODATA;
146
147 return ext2fs_find_xattr(EXT2FS_XATTR_IFIRST(h), start, end, attrnamespace, uio, size, name_index, name);
148 }
149
150 static int
151 ext2fs_get_block_xattr(struct inode *ip, int attrnamespace, struct uio *uio, size_t *size, uint8_t name_index, const char *name)
152 {
153 struct ext2fs_dinode *di = ip->i_din.e2fs_din;
154 uint8_t *start, *end;
155 struct ext2fs_xattr_header *h;
156 int error = 0;
157 struct buf *bp = NULL;
158 daddr_t xblk;
159
160 xblk = di->e2di_facl;
161 if (EXT2F_HAS_INCOMPAT_FEATURE(ip, EXT2F_INCOMPAT_64BIT))
162 xblk |= (((daddr_t)di->e2di_facl_high) << 32);
163
164 /* don't do anything if no attr block was allocated */
165 if (xblk == 0)
166 return 0;
167
168 error = bread(ip->i_devvp, fsbtodb(ip->i_e2fs, xblk), (int)ip->i_e2fs->e2fs_bsize, 0, &bp);
169 if (error)
170 goto out;
171
172 start = (uint8_t *)bp->b_data;
173 h = (struct ext2fs_xattr_header *)start;
174 end = &((uint8_t *)bp->b_data)[bp->b_bcount];
175
176 if (end <= start || fs2h32(h->h_magic) != EXT2FS_XATTR_MAGIC)
177 goto out;
178
179 error = ext2fs_find_xattr(EXT2FS_XATTR_BFIRST(h), start, end, attrnamespace, uio, size, name_index, name);
180
181 out:
182 if (bp)
183 brelse(bp, 0);
184 return error;
185 }
186 int
187 ext2fs_getextattr(void *v)
188 {
189 struct vop_getextattr_args /* {
190 const struct vnodeop_desc *a_desc;
191 struct vnode *a_vp;
192 int a_attrnamespace;
193 const char *a_name;
194 struct uio *a_uio;
195 size_t *a_size;
196 kauth_cred_t a_cred;
197 } */ *ap = v;
198 struct inode *ip = VTOI(ap->a_vp);
199 char namebuf[EXT2FS_XATTR_NAME_LEN_MAX + 1];
200 int error, i;
201 const char *prefix, *name;
202 uint8_t name_index;
203 size_t name_match, valuesize = 0;
204
205 if (ap->a_attrnamespace == EXTATTR_NAMESPACE_USER)
206 prefix = xattr_prefix_index[EXT2FS_XATTR_PREFIX_USER];
207 else
208 prefix = xattr_prefix_index[EXT2FS_XATTR_PREFIX_SYSTEM];
209 snprintf(namebuf, sizeof(namebuf), "%s%s", prefix, ap->a_name);
210
211 error = extattr_check_cred(ap->a_vp, namebuf, ap->a_cred, VREAD);
212 if (error)
213 return error;
214
215 /*
216 * Allow only offsets of zero to encourage the read/replace
217 * extended attribute semantic. Otherwise we can't guarantee
218 * atomicity, as we don't provide locks for extended attributes.
219 */
220 if (ap->a_uio != NULL && ap->a_uio->uio_offset != 0)
221 return ENXIO;
222
223 /* figure out the name index */
224 name = ap->a_name;
225 name_index = 0;
226 name_match = 0;
227 for(i=0; i < sizeof(xattr_prefix_index)/sizeof(void *); i++) {
228 prefix = xattr_prefix_index[i];
229 if (strlen(prefix) > 0 &&
230 strncmp(ap->a_name, prefix, strlen(prefix)) == 0 &&
231 name_match < strlen(prefix)) {
232 name = &ap->a_name[strlen(prefix)];
233 name_index = i;
234 name_match = strlen(prefix);
235 continue;
236 }
237 }
238
239 /* fetch the xattr */
240 error = ext2fs_get_inode_xattr(ip, ap->a_attrnamespace, ap->a_uio, &valuesize, name_index, name);
241 if (error == ENODATA) {
242 /* not found in inode, try facl */
243 error = ext2fs_get_block_xattr(ip, ap->a_attrnamespace, ap->a_uio, &valuesize, name_index, name);
244 }
245
246 if (ap->a_size != NULL)
247 *ap->a_size = valuesize;
248
249 return error;
250 }
251
252 int
253 ext2fs_setextattr(void *v)
254 {
255 #if 0
256 struct vop_setextattr_args /* {
257 const struct vnodeop_desc *a_desc;
258 struct vnode *a_vp;
259 int a_attrnamespace;
260 const char *a_name;
261 struct uio *a_uio;
262 kauth_cred_t a_cred;
263 } */ *ap = v;
264
265 /* XXX set EXT2F_COMPAT_EXTATTR in superblock after successful set */
266 #endif
267
268 /* XXX Not implemented */
269 return EOPNOTSUPP;
270 }
271
272 static int
273 ext2fs_list_xattr(struct ext2fs_xattr_entry *e, uint8_t *end, int attrnamespace, int flags, struct uio *uio, size_t *size)
274 {
275 char name[EXT2FS_XATTR_NAME_LEN_MAX + 1];
276 uint8_t len;
277 int error;
278 const char *prefix;
279
280 /*
281 * Individual entries follow the header. Each is aligned on 4-byte
282 * boundary.
283 */
284 for(; !EXT2FS_XATTR_IS_LAST_ENTRY(e, end); e = EXT2FS_XATTR_NEXT(e)) {
285 /*
286 * Only EXT2FS_XATTR_PREFIX_USER is USER, anything else
287 * is considered SYSTEM.
288 */
289 if ((attrnamespace == EXTATTR_NAMESPACE_USER && e->e_name_index != EXT2FS_XATTR_PREFIX_USER) ||
290 (attrnamespace == EXTATTR_NAMESPACE_SYSTEM && e->e_name_index == EXT2FS_XATTR_PREFIX_USER)) {
291 continue;
292 }
293
294 if (e->e_name_index <= sizeof(xattr_prefix_index)/sizeof(void *))
295 prefix = xattr_prefix_index[e->e_name_index];
296 else
297 prefix = "";
298
299 len = snprintf(name, sizeof(name), "%s%.*s",
300 prefix,
301 e->e_name_len, e->e_name);
302
303 if (uio != NULL) {
304 if (flags & EXTATTR_LIST_LENPREFIX) {
305 /* write name length */
306 uiomove(&len, sizeof(uint8_t), uio);
307 } else {
308 /* include trailing NUL */
309 len++;
310 }
311
312 error = uiomove(name, len, uio);
313 if (error)
314 return error;
315
316 *size += len;
317 }
318 }
319
320 return 0;
321 }
322
323 static int
324 ext2fs_list_inode_xattr(struct inode *ip, int attrnamespace, int flags, struct uio *uio, size_t *size)
325 {
326 struct ext2fs_dinode *di = ip->i_din.e2fs_din;
327 void *start, *end;
328 struct ext2fs_xattr_ibody_header *h;
329
330 start = &((uint8_t *)di)[EXT2_REV0_DINODE_SIZE + di->e2di_extra_isize];
331 h = start;
332 end = &((uint8_t *)di)[EXT2_DINODE_SIZE(ip->i_e2fs)];
333
334 if (end <= start || fs2h32(h->h_magic) != EXT2FS_XATTR_MAGIC)
335 return 0;
336
337 return ext2fs_list_xattr(EXT2FS_XATTR_IFIRST(h), end, attrnamespace, flags, uio, size);
338 }
339
340 static int
341 ext2fs_list_block_xattr(struct inode *ip, int attrnamespace, int flags, struct uio *uio, size_t *size)
342 {
343 struct ext2fs_dinode *di = ip->i_din.e2fs_din;
344 void *end;
345 struct ext2fs_xattr_header *h;
346 int error = 0;
347 struct buf *bp = NULL;
348 daddr_t xblk;
349
350 xblk = di->e2di_facl;
351 if (EXT2F_HAS_INCOMPAT_FEATURE(ip, EXT2F_INCOMPAT_64BIT))
352 xblk |= (((daddr_t)di->e2di_facl_high) << 32);
353
354 /* don't do anything if no attr block was allocated */
355 if (xblk == 0)
356 return 0;
357
358 error = bread(ip->i_devvp, fsbtodb(ip->i_e2fs, xblk), (int)ip->i_e2fs->e2fs_bsize, 0, &bp);
359 if (error)
360 goto out;
361
362 h = (struct ext2fs_xattr_header *)bp->b_data;
363 end = &((uint8_t *)bp->b_data)[bp->b_bcount];
364
365 if (end <= (void *)h || fs2h32(h->h_magic) != EXT2FS_XATTR_MAGIC)
366 goto out;
367
368 error = ext2fs_list_xattr(EXT2FS_XATTR_BFIRST(h), end, attrnamespace, flags, uio, size);
369
370 out:
371 if (bp)
372 brelse(bp, 0);
373 return error;
374 }
375
376 int
377 ext2fs_listextattr(void *v)
378 {
379 struct vop_listextattr_args /* {
380 const struct vnodeop_desc *a_desc;
381 struct vnode *a_vp;
382 int a_attrnamespace;
383 struct uio *a_uio;
384 size_t *a_size;
385 int a_flag;
386 kauth_cred_t a_cred;
387 } */ *ap = v;
388 struct inode *ip = VTOI(ap->a_vp);
389 int error;
390 const char *prefix;
391 size_t listsize = 0;
392
393 if (!EXT2F_HAS_COMPAT_FEATURE(ip, EXT2F_COMPAT_EXTATTR)) {
394 /* no EA on the filesystem */
395 goto out;
396 }
397
398 /*
399 * XXX: We can move this inside the loop and iterate on individual
400 * attributes.
401 */
402 if (ap->a_attrnamespace == EXTATTR_NAMESPACE_USER)
403 prefix = xattr_prefix_index[EXT2FS_XATTR_PREFIX_USER];
404 else
405 prefix = xattr_prefix_index[EXT2FS_XATTR_PREFIX_SYSTEM];
406 error = extattr_check_cred(ap->a_vp, prefix, ap->a_cred, VREAD);
407 if (error)
408 return error;
409
410 /*
411 * Allow only offsets of zero to encourage the read/replace
412 * extended attribute semantic. Otherwise we can't guarantee
413 * atomicity, as we don't provide locks for extended attributes.
414 * XXX revisit - vnode lock enough?
415 */
416 if (ap->a_uio != NULL && ap->a_uio->uio_offset != 0)
417 return ENXIO;
418
419 /* fetch inode xattrs */
420 error = ext2fs_list_inode_xattr(ip, ap->a_attrnamespace, ap->a_flag, ap->a_uio, &listsize);
421 if (error)
422 return error;
423
424 error = ext2fs_list_block_xattr(ip, ap->a_attrnamespace, ap->a_flag, ap->a_uio, &listsize);
425 if (error)
426 return error;
427
428 out:
429 if (ap->a_size != NULL)
430 *ap->a_size = listsize;
431
432 return 0;
433 }
434
435 int
436 ext2fs_deleteextattr(void *v)
437 {
438 #if 0
439 struct vop_deleteextattr_args /* {
440 const struct vnodeop_desc *a_desc;
441 struct vnode *a_vp;
442 int a_attrnamespace;
443 const char *a_name;
444 kauth_cred_t a_cred;
445 } */ *ap = v;
446 #endif
447
448 /* XXX Not implemented */
449 return EOPNOTSUPP;
450 }
451