1 /*- 2 * SPDX-License-Identifier: BSD-3-Clause 3 * 4 * Copyright (c) 1992, 1993, 1994 5 * The Regents of the University of California. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of the University nor the names of its contributors 16 * may be used to endorse or promote products derived from this software 17 * without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32 #include <sys/types.h> 33 #include <sys/capsicum.h> 34 #include <sys/stat.h> 35 36 #include <assert.h> 37 #include <capsicum_helpers.h> 38 #include <err.h> 39 #include <errno.h> 40 #include <fcntl.h> 41 #include <fts.h> 42 #include <getopt.h> 43 #include <libgen.h> 44 #include <limits.h> 45 #include <signal.h> 46 #include <stdbool.h> 47 #include <stdio.h> 48 #include <stdlib.h> 49 #include <string.h> 50 #include <unistd.h> 51 52 static volatile sig_atomic_t siginfo; 53 54 #define OPT_DEREF_UNSAFE (CHAR_MAX + 1) 55 56 static void usage(void) __dead2; 57 58 static void 59 siginfo_handler(int sig __unused) 60 { 61 62 siginfo = 1; 63 } 64 65 /* 66 * A path needs its own pre-opened directory descriptor unless it is a 67 * single path component that can be resolved directly relative to the 68 * base directory descriptor. Anything containing a '/' (an absolute 69 * path, or a relative path with a directory component) has its parent 70 * directory opened separately, so that in capability mode the final 71 * component is always reached relative to its immediate parent. 72 */ 73 static bool 74 needs_own_fd(const char *path) 75 { 76 77 return (strchr(path, '/') != NULL); 78 } 79 80 /* 81 * Open a directory descriptor for the parent of "path", and return in 82 * "*base" a pointer to the final path component (relative to that 83 * descriptor). "*base" points into the storage of "path". 84 */ 85 static int 86 open_base(char *path, char **base) 87 { 88 char *dir, *bn, *pathcopy; 89 int fd; 90 91 /* 92 * dirname() and basename() may modify their argument and may 93 * return a pointer to internal storage, so operate on copies and 94 * duplicate basename()'s result for the caller. 95 */ 96 if ((pathcopy = strdup(path)) == NULL) 97 err(1, "strdup"); 98 dir = dirname(pathcopy); 99 fd = open(dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC); 100 free(pathcopy); 101 102 if ((pathcopy = strdup(path)) == NULL) 103 err(1, "strdup"); 104 bn = basename(pathcopy); 105 if ((*base = strdup(bn)) == NULL) 106 err(1, "strdup"); 107 free(pathcopy); 108 109 return (fd); 110 } 111 112 static int 113 chflags_fts(int dirfd, char **paths, int fts_options, u_long set, u_long clear, 114 int oct, int Rflag, int fflag, int vflag) 115 { 116 FTS *ftsp; 117 FTSENT *p; 118 u_long newflags; 119 int e, rval; 120 121 if ((ftsp = fts_openat(dirfd, paths, fts_options, NULL)) == NULL) 122 err(1, NULL); 123 124 for (rval = 0; errno = 0, (p = fts_read(ftsp)) != NULL;) { 125 int atflag; 126 127 if ((fts_options & FTS_LOGICAL) || 128 ((fts_options & FTS_COMFOLLOW) && 129 p->fts_level == FTS_ROOTLEVEL)) 130 atflag = 0; 131 else 132 atflag = AT_SYMLINK_NOFOLLOW; 133 134 switch (p->fts_info) { 135 case FTS_D: /* Change it at FTS_DP if we're recursive. */ 136 if (!Rflag) 137 fts_set(ftsp, p, FTS_SKIP); 138 continue; 139 case FTS_DNR: /* Warn, chflags. */ 140 warnx("%s: %s", p->fts_path, strerror(p->fts_errno)); 141 rval = 1; 142 break; 143 case FTS_ERR: /* Warn, continue. */ 144 case FTS_NS: 145 warnx("%s: %s", p->fts_path, strerror(p->fts_errno)); 146 rval = 1; 147 continue; 148 default: 149 break; 150 } 151 if (oct) 152 newflags = set; 153 else 154 newflags = (p->fts_statp->st_flags | set) & clear; 155 if (newflags == p->fts_statp->st_flags) 156 continue; 157 if (chflagsat(p->fts_parent->fts_dirfd, p->fts_name, newflags, 158 atflag) == -1) { 159 e = errno; 160 if (!fflag) { 161 warnc(e, "%s", p->fts_path); 162 rval = 1; 163 } 164 if (siginfo) { 165 (void)printf("%s: %s\n", p->fts_path, 166 strerror(e)); 167 siginfo = 0; 168 } 169 } else if (vflag || siginfo) { 170 (void)printf("%s", p->fts_path); 171 if (vflag > 1 || siginfo) 172 (void)printf(": 0%lo -> 0%lo", 173 (u_long)p->fts_statp->st_flags, 174 newflags); 175 (void)printf("\n"); 176 siginfo = 0; 177 } 178 } 179 if (errno) 180 err(1, "fts_read"); 181 (void)fts_close(ftsp); 182 return (rval); 183 } 184 185 int 186 main(int argc, char *argv[]) 187 { 188 static const struct option longopts[] = { 189 { "dereference-links-unsafely", no_argument, NULL, 190 OPT_DEREF_UNSAFE }, 191 { NULL, 0, NULL, 0 } 192 }; 193 u_long clear, set; 194 long val; 195 int Hflag, Lflag, Rflag, fflag, hflag, vflag, xflag, unsafe; 196 int ch, fts_options, oct, rval; 197 int cwd_fd, i, nrel, nown; 198 int *ownfd; 199 char **ownbase; 200 char *flags, *ep; 201 char **relpaths, *twopath[2]; 202 203 Hflag = Lflag = Rflag = fflag = hflag = vflag = xflag = unsafe = 0; 204 while ((ch = getopt_long(argc, argv, "HLPRfhvx", longopts, 205 NULL)) != -1) 206 switch (ch) { 207 case 'H': 208 Hflag = 1; 209 Lflag = 0; 210 break; 211 case 'L': 212 Lflag = 1; 213 Hflag = 0; 214 break; 215 case 'P': 216 Hflag = Lflag = 0; 217 break; 218 case 'R': 219 Rflag = 1; 220 break; 221 case 'f': 222 fflag = 1; 223 break; 224 case 'h': 225 hflag = 1; 226 break; 227 case 'v': 228 vflag++; 229 break; 230 case 'x': 231 xflag = 1; 232 break; 233 case OPT_DEREF_UNSAFE: 234 unsafe = 1; 235 break; 236 case '?': 237 default: 238 usage(); 239 } 240 argv += optind; 241 argc -= optind; 242 243 if (argc < 2) 244 usage(); 245 246 (void)signal(SIGINFO, siginfo_handler); 247 248 if (Rflag) { 249 if (hflag) 250 errx(1, "the -R and -h options may not be " 251 "specified together."); 252 if (Lflag) { 253 fts_options = FTS_LOGICAL; 254 } else { 255 fts_options = FTS_PHYSICAL; 256 257 if (Hflag) { 258 fts_options |= FTS_COMFOLLOW; 259 } 260 } 261 } else if (hflag) { 262 fts_options = FTS_PHYSICAL; 263 } else { 264 fts_options = FTS_LOGICAL; 265 } 266 if (xflag) 267 fts_options |= FTS_XDEV; 268 269 flags = *argv; 270 if (*flags >= '0' && *flags <= '7') { 271 errno = 0; 272 val = strtol(flags, &ep, 8); 273 if (val < 0) 274 errno = ERANGE; 275 if (errno) 276 err(1, "invalid flags: %s", flags); 277 if (*ep) 278 errx(1, "invalid flags: %s", flags); 279 set = val; 280 oct = 1; 281 } else { 282 if (strtofflags(&flags, &set, &clear)) 283 errx(1, "invalid flag: %s", flags); 284 clear = ~clear; 285 oct = 0; 286 } 287 288 argv++; 289 argc--; 290 291 /* 292 * Pre-open a directory descriptor for every path argument, so the 293 * traversal runs through fd-relative operations. Plain relative 294 * arguments share a descriptor for the current directory; absolute 295 * paths and paths containing ".." cannot be resolved relative to 296 * another descriptor in capability mode, so each gets its own 297 * parent descriptor. 298 */ 299 if ((cwd_fd = open(".", O_RDONLY | O_DIRECTORY | O_CLOEXEC)) < 0) 300 err(1, "."); 301 302 relpaths = calloc(argc + 1, sizeof(*relpaths)); 303 ownfd = calloc(argc, sizeof(*ownfd)); 304 ownbase = calloc(argc, sizeof(*ownbase)); 305 if (relpaths == NULL || ownfd == NULL || ownbase == NULL) 306 err(1, "calloc"); 307 nrel = 0; 308 nown = 0; 309 rval = 0; 310 311 for (i = 0; i < argc; i++) { 312 char *arg = argv[i]; 313 char resolved[PATH_MAX]; 314 struct stat sb; 315 316 /* 317 * A symbolic link named on the command line is followed 318 * unless -h was given (or a purely physical walk was 319 * requested). Its target may lie outside the link's parent 320 * directory, which capability mode could not reach, so 321 * resolve the link now and operate relative to the target's 322 * own parent. 323 */ 324 if ((fts_options & (FTS_LOGICAL | FTS_COMFOLLOW)) && 325 lstat(arg, &sb) == 0 && S_ISLNK(sb.st_mode)) { 326 if (realpath(arg, resolved) == NULL) { 327 warn("%s", arg); 328 rval = 1; 329 continue; 330 } 331 arg = resolved; 332 } 333 334 if (needs_own_fd(arg)) { 335 int fd = open_base(arg, &ownbase[nown]); 336 if (fd < 0) { 337 warn("%s", argv[i]); 338 rval = 1; 339 continue; 340 } 341 ownfd[nown] = fd; 342 nown++; 343 } else { 344 /* 345 * A resolved symlink is always absolute and thus 346 * takes the branch above; only an unmodified argument 347 * reaches this point. Store argv[i], which outlives 348 * the on-stack resolved[] buffer. 349 */ 350 assert(arg != resolved); 351 relpaths[nrel++] = argv[i]; 352 } 353 } 354 relpaths[nrel] = NULL; 355 356 if (caph_limit_stdio() < 0) 357 err(1, "caph_limit_stdio"); 358 /* 359 * With --dereference-links-unsafely the traversal may follow a 360 * symlink to a file outside the hierarchy named on the command 361 * line, which capability mode would block, so skip caph_enter() in 362 * that case. 363 */ 364 if (!unsafe && caph_enter() < 0) 365 err(1, "caph_enter"); 366 367 /* Process all plain relative paths together under cwd_fd. */ 368 if (nrel > 0) 369 rval |= chflags_fts(cwd_fd, relpaths, fts_options, set, clear, 370 oct, Rflag, fflag, vflag); 371 372 /* Process each absolute / ".."-containing path under its own fd. */ 373 for (i = 0; i < nown; i++) { 374 twopath[0] = ownbase[i]; 375 twopath[1] = NULL; 376 rval |= chflags_fts(ownfd[i], twopath, fts_options, set, 377 clear, oct, Rflag, fflag, vflag); 378 free(ownbase[i]); 379 } 380 free(relpaths); 381 free(ownfd); 382 free(ownbase); 383 exit(rval); 384 } 385 386 static void 387 usage(void) 388 { 389 (void)fprintf(stderr, 390 "usage: chflags [-fhvx] [-R [-H | -L | -P]] " 391 "[--dereference-links-unsafely] flags file ...\n"); 392 exit(1); 393 } 394