1 /* $NetBSD: ext2fs_xattr.c,v 1.5 2020/05/16 18:31:53 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.5 2020/05/16 18:31:53 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, 72 int attrnamespace, struct uio *uio, size_t *size, uint8_t name_index, 73 const char *name) 74 { 75 uint8_t *value; 76 int error; 77 size_t value_offs, value_len, len, old_len; 78 79 /* 80 * Individual entries follow the header. Each is aligned on 4-byte 81 * boundary. 82 */ 83 for(; !EXT2FS_XATTR_IS_LAST_ENTRY(e, end); e = EXT2FS_XATTR_NEXT(e)) { 84 /* 85 * Only EXT2FS_XATTR_PREFIX_USER is USER, anything else 86 * is considered SYSTEM. 87 */ 88 if ((attrnamespace == EXTATTR_NAMESPACE_USER 89 && e->e_name_index != EXT2FS_XATTR_PREFIX_USER) || 90 (attrnamespace == EXTATTR_NAMESPACE_SYSTEM 91 && e->e_name_index == EXT2FS_XATTR_PREFIX_USER)) { 92 continue; 93 } 94 95 if (e->e_name_index != name_index || 96 e->e_name_len != strlen(name) || 97 strncmp(e->e_name, name, e->e_name_len) != 0) 98 continue; 99 100 value_offs = fs2h32(e->e_value_offs); 101 value_len = fs2h32(e->e_value_size); 102 value = &start[value_offs]; 103 104 /* make sure the value offset are sane */ 105 if (&value[value_len] > end) 106 return EINVAL; 107 108 if (uio != NULL) { 109 /* 110 * Figure out maximum to transfer -- use buffer size 111 * and local data limit. 112 */ 113 len = MIN(uio->uio_resid, value_len); 114 old_len = uio->uio_resid; 115 uio->uio_resid = len; 116 117 uio->uio_resid = old_len - (len - uio->uio_resid); 118 119 error = uiomove(value, value_len, uio); 120 if (error) 121 return error; 122 } 123 124 /* full data size */ 125 *size += value_len; 126 127 goto found; 128 } 129 130 /* requested attribute not found */ 131 return ENODATA; 132 133 found: 134 return 0; 135 } 136 137 static int 138 ext2fs_get_inode_xattr(struct inode *ip, int attrnamespace, struct uio *uio, 139 size_t *size, uint8_t name_index, const char *name) 140 { 141 struct ext2fs_dinode *di = ip->i_din.e2fs_din; 142 struct ext2fs_xattr_ibody_header *h; 143 uint8_t *start, *end; 144 145 start = &((uint8_t *)di)[EXT2_REV0_DINODE_SIZE + di->e2di_extra_isize]; 146 h = (struct ext2fs_xattr_ibody_header *)start; 147 end = &((uint8_t *)di)[EXT2_DINODE_SIZE(ip->i_e2fs)]; 148 149 if (end <= start || fs2h32(h->h_magic) != EXT2FS_XATTR_MAGIC) 150 return ENODATA; 151 152 return ext2fs_find_xattr(EXT2FS_XATTR_IFIRST(h), start, end, 153 attrnamespace, uio, size, name_index, name); 154 } 155 156 static int 157 ext2fs_get_block_xattr(struct inode *ip, int attrnamespace, struct uio *uio, 158 size_t *size, uint8_t name_index, const char *name) 159 { 160 struct ext2fs_dinode *di = ip->i_din.e2fs_din; 161 uint8_t *start, *end; 162 struct ext2fs_xattr_header *h; 163 int error = 0; 164 struct buf *bp = NULL; 165 daddr_t xblk; 166 167 xblk = di->e2di_facl; 168 if (EXT2F_HAS_INCOMPAT_FEATURE(ip->i_e2fs, EXT2F_INCOMPAT_64BIT)) 169 xblk |= (((daddr_t)di->e2di_facl_high) << 32); 170 171 /* don't do anything if no attr block was allocated */ 172 if (xblk == 0) 173 return 0; 174 175 error = bread(ip->i_devvp, fsbtodb(ip->i_e2fs, xblk), 176 (int)ip->i_e2fs->e2fs_bsize, 0, &bp); 177 if (error) 178 goto out; 179 180 start = (uint8_t *)bp->b_data; 181 h = (struct ext2fs_xattr_header *)start; 182 end = &((uint8_t *)bp->b_data)[bp->b_bcount]; 183 184 if (end <= start || fs2h32(h->h_magic) != EXT2FS_XATTR_MAGIC) 185 goto out; 186 187 error = ext2fs_find_xattr(EXT2FS_XATTR_BFIRST(h), start, end, 188 attrnamespace, uio, size, name_index, name); 189 190 out: 191 if (bp) 192 brelse(bp, 0); 193 return error; 194 } 195 int 196 ext2fs_getextattr(void *v) 197 { 198 struct vop_getextattr_args /* { 199 const struct vnodeop_desc *a_desc; 200 struct vnode *a_vp; 201 int a_attrnamespace; 202 const char *a_name; 203 struct uio *a_uio; 204 size_t *a_size; 205 kauth_cred_t a_cred; 206 } */ *ap = v; 207 struct inode *ip = VTOI(ap->a_vp); 208 int error; 209 const char *prefix, *name; 210 uint8_t name_index; 211 size_t name_match, valuesize = 0; 212 213 error = extattr_check_cred(ap->a_vp, ap->a_attrnamespace, ap->a_cred, 214 VREAD); 215 if (error) 216 return error; 217 218 /* 219 * Allow only offsets of zero to encourage the read/replace 220 * extended attribute semantic. Otherwise we can't guarantee 221 * atomicity, as we don't provide locks for extended attributes. 222 */ 223 if (ap->a_uio != NULL && ap->a_uio->uio_offset != 0) 224 return ENXIO; 225 226 /* figure out the name index */ 227 name = ap->a_name; 228 name_index = 0; 229 name_match = 0; 230 for(size_t i = 0; i < __arraycount(xattr_prefix_index); i++) { 231 prefix = xattr_prefix_index[i]; 232 size_t l = strlen(prefix); 233 if (l > 0 && strncmp(ap->a_name, prefix, l) == 0 && 234 name_match < l) { 235 name = &ap->a_name[l]; 236 name_index = i; 237 name_match = l; 238 continue; 239 } 240 } 241 242 /* fetch the xattr */ 243 error = ext2fs_get_inode_xattr(ip, ap->a_attrnamespace, ap->a_uio, 244 &valuesize, name_index, name); 245 if (error == ENODATA) { 246 /* not found in inode, try facl */ 247 error = ext2fs_get_block_xattr(ip, ap->a_attrnamespace, 248 ap->a_uio, &valuesize, name_index, name); 249 } 250 251 if (ap->a_size != NULL) 252 *ap->a_size = valuesize; 253 254 return error; 255 } 256 257 int 258 ext2fs_setextattr(void *v) 259 { 260 #if 0 261 struct vop_setextattr_args /* { 262 const struct vnodeop_desc *a_desc; 263 struct vnode *a_vp; 264 int a_attrnamespace; 265 const char *a_name; 266 struct uio *a_uio; 267 kauth_cred_t a_cred; 268 } */ *ap = v; 269 270 /* XXX set EXT2F_COMPAT_EXTATTR in superblock after successful set */ 271 #endif 272 273 /* XXX Not implemented */ 274 return EOPNOTSUPP; 275 } 276 277 static int 278 ext2fs_list_xattr(struct ext2fs_xattr_entry *e, uint8_t *end, 279 int attrnamespace, int flags, struct uio *uio, size_t *size) 280 { 281 char name[EXT2FS_XATTR_NAME_LEN_MAX + 1]; 282 uint8_t len; 283 int error; 284 const char *prefix; 285 286 /* 287 * Individual entries follow the header. Each is aligned on 4-byte 288 * boundary. 289 */ 290 for(; !EXT2FS_XATTR_IS_LAST_ENTRY(e, end); e = EXT2FS_XATTR_NEXT(e)) { 291 /* 292 * Only EXT2FS_XATTR_PREFIX_USER is USER, anything else 293 * is considered SYSTEM. 294 */ 295 if ((attrnamespace == EXTATTR_NAMESPACE_USER 296 && e->e_name_index != EXT2FS_XATTR_PREFIX_USER) || 297 (attrnamespace == EXTATTR_NAMESPACE_SYSTEM 298 && e->e_name_index == EXT2FS_XATTR_PREFIX_USER)) { 299 continue; 300 } 301 302 if (e->e_name_index < __arraycount(xattr_prefix_index)) 303 prefix = xattr_prefix_index[e->e_name_index]; 304 else 305 prefix = ""; 306 307 len = snprintf(name, sizeof(name), "%s%.*s", 308 prefix, 309 e->e_name_len, e->e_name); 310 311 if (uio != NULL) { 312 if (flags & EXTATTR_LIST_LENPREFIX) { 313 /* write name length */ 314 error = uiomove(&len, sizeof(uint8_t), uio); 315 if (error) 316 return error; 317 } else { 318 /* include trailing NUL */ 319 len++; 320 } 321 322 error = uiomove(name, len, uio); 323 if (error) 324 return error; 325 326 *size += len; 327 } 328 } 329 330 return 0; 331 } 332 333 static int 334 ext2fs_list_inode_xattr(struct inode *ip, int attrnamespace, int flags, 335 struct uio *uio, size_t *size) 336 { 337 struct ext2fs_dinode *di = ip->i_din.e2fs_din; 338 void *start, *end; 339 struct ext2fs_xattr_ibody_header *h; 340 341 start = &((uint8_t *)di)[EXT2_REV0_DINODE_SIZE + di->e2di_extra_isize]; 342 h = start; 343 end = &((uint8_t *)di)[EXT2_DINODE_SIZE(ip->i_e2fs)]; 344 345 if (end <= start || fs2h32(h->h_magic) != EXT2FS_XATTR_MAGIC) 346 return 0; 347 348 return ext2fs_list_xattr(EXT2FS_XATTR_IFIRST(h), end, attrnamespace, 349 flags, uio, size); 350 } 351 352 static int 353 ext2fs_list_block_xattr(struct inode *ip, int attrnamespace, int flags, 354 struct uio *uio, size_t *size) 355 { 356 struct ext2fs_dinode *di = ip->i_din.e2fs_din; 357 void *end; 358 struct ext2fs_xattr_header *h; 359 int error = 0; 360 struct buf *bp = NULL; 361 daddr_t xblk; 362 363 xblk = di->e2di_facl; 364 if (EXT2F_HAS_INCOMPAT_FEATURE(ip->i_e2fs, EXT2F_INCOMPAT_64BIT)) 365 xblk |= (((daddr_t)di->e2di_facl_high) << 32); 366 367 /* don't do anything if no attr block was allocated */ 368 if (xblk == 0) 369 return 0; 370 371 error = bread(ip->i_devvp, fsbtodb(ip->i_e2fs, xblk), 372 (int)ip->i_e2fs->e2fs_bsize, 0, &bp); 373 if (error) 374 goto out; 375 376 h = (struct ext2fs_xattr_header *)bp->b_data; 377 end = &((uint8_t *)bp->b_data)[bp->b_bcount]; 378 379 if (end <= (void *)h || fs2h32(h->h_magic) != EXT2FS_XATTR_MAGIC) 380 goto out; 381 382 error = ext2fs_list_xattr(EXT2FS_XATTR_BFIRST(h), end, attrnamespace, 383 flags, uio, size); 384 385 out: 386 if (bp) 387 brelse(bp, 0); 388 return error; 389 } 390 391 int 392 ext2fs_listextattr(void *v) 393 { 394 struct vop_listextattr_args /* { 395 const struct vnodeop_desc *a_desc; 396 struct vnode *a_vp; 397 int a_attrnamespace; 398 struct uio *a_uio; 399 size_t *a_size; 400 int a_flag; 401 kauth_cred_t a_cred; 402 } */ *ap = v; 403 struct inode *ip = VTOI(ap->a_vp); 404 int error; 405 size_t listsize = 0; 406 407 if (!EXT2F_HAS_COMPAT_FEATURE(ip->i_e2fs, EXT2F_COMPAT_EXTATTR)) { 408 /* no EA on the filesystem */ 409 goto out; 410 } 411 412 error = extattr_check_cred(ap->a_vp, ap->a_attrnamespace, ap->a_cred, 413 VREAD); 414 if (error) 415 return error; 416 417 /* 418 * Allow only offsets of zero to encourage the read/replace 419 * extended attribute semantic. Otherwise we can't guarantee 420 * atomicity, as we don't provide locks for extended attributes. 421 * XXX revisit - vnode lock enough? 422 */ 423 if (ap->a_uio != NULL && ap->a_uio->uio_offset != 0) 424 return ENXIO; 425 426 /* fetch inode xattrs */ 427 error = ext2fs_list_inode_xattr(ip, ap->a_attrnamespace, ap->a_flag, 428 ap->a_uio, &listsize); 429 if (error) 430 return error; 431 432 error = ext2fs_list_block_xattr(ip, ap->a_attrnamespace, ap->a_flag, 433 ap->a_uio, &listsize); 434 if (error) 435 return error; 436 437 out: 438 if (ap->a_size != NULL) 439 *ap->a_size = listsize; 440 441 return 0; 442 } 443 444 int 445 ext2fs_deleteextattr(void *v) 446 { 447 #if 0 448 struct vop_deleteextattr_args /* { 449 const struct vnodeop_desc *a_desc; 450 struct vnode *a_vp; 451 int a_attrnamespace; 452 const char *a_name; 453 kauth_cred_t a_cred; 454 } */ *ap = v; 455 #endif 456 457 /* XXX Not implemented */ 458 return EOPNOTSUPP; 459 } 460