1 /* $NetBSD: vfs_getcwd.c,v 1.61 2021/06/29 22:39:21 dholland Exp $ */ 2 3 /*- 4 * Copyright (c) 1999, 2020 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Bill Sommerfeld. 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: vfs_getcwd.c,v 1.61 2021/06/29 22:39:21 dholland Exp $"); 34 35 #include <sys/param.h> 36 #include <sys/systm.h> 37 #include <sys/namei.h> 38 #include <sys/filedesc.h> 39 #include <sys/kernel.h> 40 #include <sys/file.h> 41 #include <sys/stat.h> 42 #include <sys/vnode.h> 43 #include <sys/mount.h> 44 #include <sys/proc.h> 45 #include <sys/uio.h> 46 #include <sys/kmem.h> 47 #include <sys/dirent.h> 48 #include <sys/kauth.h> 49 50 #include <ufs/ufs/dir.h> /* XXX only for DIRBLKSIZ */ 51 52 #include <sys/syscallargs.h> 53 54 /* 55 * Vnode variable naming conventions in this file: 56 * 57 * rvp: the current root we're aiming towards. 58 * lvp, *lvpp: the "lower" vnode 59 * uvp, *uvpp: the "upper" vnode. 60 * 61 * Since all the vnodes we're dealing with are directories, and the 62 * lookups are going *up* in the filesystem rather than *down*, the 63 * usual "pvp" (parent) or "dvp" (directory) naming conventions are 64 * too confusing. 65 */ 66 67 /* 68 * XXX Will infinite loop in certain cases if a directory read reliably 69 * returns EINVAL on last block. 70 * XXX is EINVAL the right thing to return if a directory is malformed? 71 */ 72 73 /* 74 * XXX Untested vs. mount -o union; probably does the wrong thing. 75 */ 76 77 /* 78 * Find parent vnode of *lvpp, return in *uvpp 79 * 80 * If we care about the name, scan it looking for name of directory 81 * entry pointing at lvp. 82 * 83 * Place the name in the buffer which starts at bufp, immediately 84 * before *bpp, and move bpp backwards to point at the start of it. 85 * 86 * On entry, *lvpp is a locked vnode reference; on exit, it is vput and NULL'ed 87 * On exit, *uvpp is either NULL or is a locked vnode reference. 88 */ 89 static int 90 getcwd_scandir(struct vnode *lvp, struct vnode **uvpp, char **bpp, 91 char *bufp, struct lwp *l) 92 { 93 int error = 0; 94 int eofflag; 95 off_t off; 96 int tries; 97 struct uio uio; 98 struct iovec iov; 99 char *dirbuf = NULL; 100 int dirbuflen; 101 ino_t fileno; 102 struct vattr va; 103 struct vnode *uvp = NULL; 104 kauth_cred_t cred = l->l_cred; 105 struct componentname cn; 106 int len, reclen; 107 tries = 0; 108 109 /* Need exclusive for UFS VOP_GETATTR (itimes) & VOP_LOOKUP. */ 110 KASSERT(VOP_ISLOCKED(lvp) == LK_EXCLUSIVE); 111 112 /* 113 * If we want the filename, get some info we need while the 114 * current directory is still locked. 115 */ 116 if (bufp != NULL) { 117 error = VOP_GETATTR(lvp, &va, cred); 118 if (error) { 119 VOP_UNLOCK(lvp); 120 *uvpp = NULL; 121 return error; 122 } 123 } 124 125 /* 126 * Ok, we have to do it the hard way.. 127 * Next, get parent vnode using lookup of .. 128 */ 129 cn.cn_nameiop = LOOKUP; 130 cn.cn_flags = ISLASTCN | ISDOTDOT | RDONLY; 131 cn.cn_cred = cred; 132 cn.cn_nameptr = ".."; 133 cn.cn_namelen = 2; 134 135 /* At this point, lvp is locked */ 136 error = VOP_LOOKUP(lvp, uvpp, &cn); 137 VOP_UNLOCK(lvp); 138 if (error) { 139 *uvpp = NULL; 140 return error; 141 } 142 uvp = *uvpp; 143 /* If we don't care about the pathname, we're done */ 144 if (bufp == NULL) { 145 return 0; 146 } 147 148 fileno = va.va_fileid; 149 150 /* I guess UFS_DIRBLKSIZ is a good guess at a good size to use? */ 151 dirbuflen = UFS_DIRBLKSIZ; 152 if (dirbuflen < va.va_blocksize) 153 dirbuflen = va.va_blocksize; 154 dirbuf = kmem_alloc(dirbuflen, KM_SLEEP); 155 156 /* Now lvp is unlocked, try to lock uvp */ 157 error = vn_lock(uvp, LK_SHARED); 158 if (error) { 159 vrele(uvp); 160 *uvpp = NULL; 161 return error; 162 } 163 164 #if 0 165 unionread: 166 #endif 167 off = 0; 168 do { 169 /* call VOP_READDIR of parent */ 170 iov.iov_base = dirbuf; 171 iov.iov_len = dirbuflen; 172 173 uio.uio_iov = &iov; 174 uio.uio_iovcnt = 1; 175 uio.uio_offset = off; 176 uio.uio_resid = dirbuflen; 177 uio.uio_rw = UIO_READ; 178 UIO_SETUP_SYSSPACE(&uio); 179 180 eofflag = 0; 181 182 error = VOP_READDIR(uvp, &uio, cred, &eofflag, 0, 0); 183 184 off = uio.uio_offset; 185 186 /* 187 * Try again if NFS tosses its cookies. 188 * XXX this can still loop forever if the directory is busted 189 * such that the second or subsequent page of it always 190 * returns EINVAL 191 */ 192 if ((error == EINVAL) && (tries < 3)) { 193 off = 0; 194 tries++; 195 continue; /* once more, with feeling */ 196 } 197 198 if (!error) { 199 char *cpos; 200 struct dirent *dp; 201 202 cpos = dirbuf; 203 tries = 0; 204 205 /* scan directory page looking for matching vnode */ 206 for (len = (dirbuflen - uio.uio_resid); len > 0; 207 len -= reclen) { 208 dp = (struct dirent *) cpos; 209 reclen = dp->d_reclen; 210 211 /* check for malformed directory.. */ 212 if (reclen < _DIRENT_MINSIZE(dp) || 213 reclen > len) { 214 error = EINVAL; 215 goto out; 216 } 217 /* 218 * XXX should perhaps do VOP_LOOKUP to 219 * check that we got back to the right place, 220 * but getting the locking games for that 221 * right would be heinous. 222 */ 223 if ((dp->d_type != DT_WHT) && 224 (dp->d_fileno == fileno)) { 225 char *bp = *bpp; 226 227 bp -= dp->d_namlen; 228 if (bp <= bufp) { 229 error = ERANGE; 230 goto out; 231 } 232 memcpy(bp, dp->d_name, dp->d_namlen); 233 error = 0; 234 *bpp = bp; 235 goto out; 236 } 237 cpos += reclen; 238 } 239 } else 240 goto out; 241 } while (!eofflag); 242 #if 0 243 /* 244 * Deal with mount -o union, which unions only the 245 * root directory of the mount. 246 */ 247 if ((uvp->v_vflag & VV_ROOT) && 248 (uvp->v_mount->mnt_flag & MNT_UNION)) { 249 struct vnode *tvp = uvp; 250 251 uvp = uvp->v_mount->mnt_vnodecovered; 252 vput(tvp); 253 vref(uvp); 254 *uvpp = uvp; 255 vn_lock(uvp, LK_SHARED | LK_RETRY); 256 goto unionread; 257 } 258 #endif 259 error = ENOENT; 260 261 out: 262 VOP_UNLOCK(uvp); 263 kmem_free(dirbuf, dirbuflen); 264 return error; 265 } 266 267 /* 268 * common routine shared by sys___getcwd() and vn_isunder() 269 */ 270 int 271 getcwd_common(struct vnode *lvp, struct vnode *rvp, char **bpp, char *bufp, 272 int limit, int flags, struct lwp *l) 273 { 274 struct cwdinfo *cwdi = l->l_proc->p_cwdi; 275 kauth_cred_t cred = l->l_cred; 276 struct vnode *uvp = NULL; 277 char *bp = NULL; 278 int error; 279 accmode_t accmode = VEXEC; 280 281 error = 0; 282 if (rvp == NULL) { 283 rvp = cwdi->cwdi_rdir; 284 if (rvp == NULL) 285 rvp = rootvnode; 286 } 287 288 vref(rvp); 289 vref(lvp); 290 291 /* 292 * Error handling invariant: 293 * Before a `goto out': 294 * lvp is either NULL, or held. 295 * uvp is either NULL, or held. 296 */ 297 298 if (bufp) 299 bp = *bpp; 300 301 /* 302 * this loop will terminate when one of the following happens: 303 * - we hit the root 304 * - getdirentries or lookup fails 305 * - we run out of space in the buffer. 306 */ 307 if (lvp == rvp) { 308 if (bp) 309 *(--bp) = '/'; 310 goto out; 311 } 312 do { 313 /* 314 * access check here is optional, depending on 315 * whether or not caller cares. 316 */ 317 int chkaccess = (flags & GETCWD_CHECK_ACCESS); 318 bool locked = false; 319 320 /* 321 * step up if we're a covered vnode.. 322 * check access on the first vnode only. 323 */ 324 if (lvp->v_vflag & VV_ROOT) { 325 vn_lock(lvp, LK_SHARED | LK_RETRY); 326 if (chkaccess) { 327 error = VOP_ACCESS(lvp, accmode, cred); 328 if (error) { 329 VOP_UNLOCK(lvp); 330 goto out; 331 } 332 chkaccess = 0; 333 } 334 while (lvp->v_vflag & VV_ROOT) { 335 struct vnode *tvp; 336 337 if (lvp == rvp) { 338 VOP_UNLOCK(lvp); 339 goto out; 340 } 341 342 tvp = lvp->v_mount->mnt_vnodecovered; 343 /* 344 * hodie natus est radici frater 345 */ 346 if (tvp == NULL) { 347 VOP_UNLOCK(lvp); 348 error = ENOENT; 349 goto out; 350 } 351 vref(tvp); 352 vput(lvp); 353 lvp = tvp; 354 if (lvp->v_vflag & VV_ROOT) 355 vn_lock(lvp, LK_SHARED | LK_RETRY); 356 } 357 } 358 359 /* Do we need to check access to the directory? */ 360 if (chkaccess && !cache_have_id(lvp)) { 361 /* Need exclusive for UFS VOP_GETATTR (itimes) & VOP_LOOKUP. */ 362 vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY); 363 error = VOP_ACCESS(lvp, accmode, cred); 364 if (error) { 365 VOP_UNLOCK(lvp); 366 goto out; 367 } 368 chkaccess = 0; 369 locked = true; 370 } 371 372 /* 373 * Look in the name cache; if that fails, look in the 374 * directory.. 375 */ 376 error = cache_revlookup(lvp, &uvp, &bp, bufp, chkaccess, 377 accmode); 378 if (error == -1) { 379 if (!locked) { 380 locked = true; 381 vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY); 382 } 383 if (lvp->v_type != VDIR) { 384 VOP_UNLOCK(lvp); 385 error = ENOTDIR; 386 goto out; 387 } 388 error = getcwd_scandir(lvp, &uvp, &bp, bufp, l); 389 /* lvp now unlocked */ 390 } else if (locked) { 391 VOP_UNLOCK(lvp); 392 } 393 if (error) 394 goto out; 395 #if DIAGNOSTIC 396 if (bufp && (bp <= bufp)) { 397 panic("getcwd: oops, went back too far"); 398 } 399 #endif 400 accmode = VEXEC | VREAD; 401 if (bp) 402 *(--bp) = '/'; 403 vrele(lvp); 404 lvp = uvp; 405 uvp = NULL; 406 limit--; 407 } while ((lvp != rvp) && (limit > 0)); 408 409 out: 410 if (bpp) 411 *bpp = bp; 412 if (uvp) 413 vrele(uvp); 414 if (lvp) 415 vrele(lvp); 416 vrele(rvp); 417 return error; 418 } 419 420 /* 421 * Check if one directory can be found inside another in the directory 422 * hierarchy. 423 * 424 * Intended to be used in chroot, chdir, fchdir, etc., to ensure that 425 * chroot() actually means something. 426 */ 427 int 428 vn_isunder(struct vnode *lvp, struct vnode *rvp, struct lwp *l) 429 { 430 int error; 431 432 error = getcwd_common(lvp, rvp, NULL, NULL, MAXPATHLEN / 2, 0, l); 433 434 if (!error) 435 return 1; 436 else 437 return 0; 438 } 439 440 /* 441 * Returns true if proc p1's root directory equal to or under p2's 442 * root directory. 443 * 444 * Intended to be used from ptrace/procfs sorts of things. 445 */ 446 447 int 448 proc_isunder(struct proc *p1, struct lwp *l2) 449 { 450 struct vnode *r1 = p1->p_cwdi->cwdi_rdir; 451 struct vnode *r2 = l2->l_proc->p_cwdi->cwdi_rdir; 452 453 if (r1 == NULL) 454 return (r2 == NULL); 455 else if (r2 == NULL) 456 return 1; 457 else 458 return vn_isunder(r1, r2, l2); 459 } 460 461 /* 462 * Find pathname of process's current directory. 463 * 464 * Use vfs vnode-to-name reverse cache; if that fails, fall back 465 * to reading directory contents. 466 */ 467 468 int 469 sys___getcwd(struct lwp *l, const struct sys___getcwd_args *uap, register_t *retval) 470 { 471 /* { 472 syscallarg(char *) bufp; 473 syscallarg(size_t) length; 474 } */ 475 476 int error; 477 char *path; 478 char *bp, *bend; 479 int len = SCARG(uap, length); 480 int lenused; 481 struct cwdinfo *cwdi; 482 483 if (len > MAXPATHLEN * 4) 484 len = MAXPATHLEN * 4; 485 else if (len < 2) 486 return ERANGE; 487 488 path = kmem_alloc(len, KM_SLEEP); 489 bp = &path[len]; 490 bend = bp; 491 *(--bp) = '\0'; 492 493 /* 494 * 5th argument here is "max number of vnodes to traverse". 495 * Since each entry takes up at least 2 bytes in the output buffer, 496 * limit it to N/2 vnodes for an N byte buffer. 497 */ 498 cwdi = l->l_proc->p_cwdi; 499 rw_enter(&cwdi->cwdi_lock, RW_READER); 500 error = getcwd_common(cwdi->cwdi_cdir, NULL, &bp, path, 501 len/2, GETCWD_CHECK_ACCESS, l); 502 rw_exit(&cwdi->cwdi_lock); 503 504 if (error) 505 goto out; 506 lenused = bend - bp; 507 *retval = lenused; 508 /* put the result into user buffer */ 509 error = copyout(bp, SCARG(uap, bufp), lenused); 510 511 out: 512 kmem_free(path, len); 513 return error; 514 } 515 516 /* 517 * Try to find a pathname for a vnode. Since there is no mapping vnode -> 518 * parent directory, this needs the namecache to succeed. Caller holds a 519 * reference to the vnode. 520 */ 521 int 522 vnode_to_path(char *path, size_t len, struct vnode *vp, struct lwp *curl, 523 struct proc *p) 524 { 525 struct proc *curp = curl->l_proc; 526 int error, lenused, elen; 527 char *bp, *bend; 528 struct vnode *dvp; 529 530 KASSERT(vrefcnt(vp) > 0); 531 532 bp = bend = &path[len]; 533 *(--bp) = '\0'; 534 535 error = cache_revlookup(vp, &dvp, &bp, path, false, 0); 536 if (error != 0) 537 return (error == -1 ? ENOENT : error); 538 539 *(--bp) = '/'; 540 error = getcwd_common(dvp, NULL, &bp, path, len / 2, 541 GETCWD_CHECK_ACCESS, curl); 542 vrele(dvp); 543 if (error != 0) 544 return error; 545 546 /* 547 * Strip off emulation path for emulated processes looking at 548 * the maps file of a process of the same emulation. (Won't 549 * work if /emul/xxx is a symlink..) 550 */ 551 if (curp->p_emul == p->p_emul && curp->p_emul->e_path != NULL) { 552 elen = strlen(curp->p_emul->e_path); 553 if (!strncmp(bp, curp->p_emul->e_path, elen)) 554 bp = &bp[elen]; 555 } 556 557 lenused = bend - bp; 558 559 memcpy(path, bp, lenused); 560 path[lenused] = '\0'; 561 562 return 0; 563 } 564