1 /*- 2 * SPDX-License-Identifier: BSD-3-Clause 3 * 4 * Copyright (c) 2002 Poul-Henning Kamp 5 * Copyright (c) 2002 Networks Associates Technology, Inc. 6 * All rights reserved. 7 * 8 * This software was developed for the FreeBSD Project by Poul-Henning Kamp 9 * and NAI Labs, the Security Research Division of Network Associates, Inc. 10 * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the 11 * DARPA CHATS research program. 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions 15 * are met: 16 * 1. Redistributions of source code must retain the above copyright 17 * notice, this list of conditions and the following disclaimer. 18 * 2. Redistributions in binary form must reproduce the above copyright 19 * notice, this list of conditions and the following disclaimer in the 20 * documentation and/or other materials provided with the distribution. 21 * 3. The names of the authors may not be used to endorse or promote 22 * products derived from this software without specific prior written 23 * permission. 24 * 25 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 28 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 29 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 30 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 31 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 34 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 35 * SUCH DAMAGE. 36 * 37 * Copyright (c) 1986, 1992, 1993 38 * The Regents of the University of California. All rights reserved. 39 * 40 * Redistribution and use in source and binary forms, with or without 41 * modification, are permitted provided that the following conditions 42 * are met: 43 * 1. Redistributions of source code must retain the above copyright 44 * notice, this list of conditions and the following disclaimer. 45 * 2. Redistributions in binary form must reproduce the above copyright 46 * notice, this list of conditions and the following disclaimer in the 47 * documentation and/or other materials provided with the distribution. 48 * 3. Neither the name of the University nor the names of its contributors 49 * may be used to endorse or promote products derived from this software 50 * without specific prior written permission. 51 * 52 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 53 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 54 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 55 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 56 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 57 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 58 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 59 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 60 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 61 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 62 * SUCH DAMAGE. 63 */ 64 65 #include <sys/cdefs.h> 66 __FBSDID("$FreeBSD$"); 67 68 #include <sys/param.h> 69 #include <sys/disk.h> 70 #include <sys/kerneldump.h> 71 #include <sys/mount.h> 72 #include <sys/stat.h> 73 74 #include <capsicum_helpers.h> 75 #include <ctype.h> 76 #include <errno.h> 77 #include <fcntl.h> 78 #include <fstab.h> 79 #include <paths.h> 80 #include <signal.h> 81 #include <stdarg.h> 82 #include <stdbool.h> 83 #include <stdio.h> 84 #include <stdlib.h> 85 #include <string.h> 86 #include <syslog.h> 87 #include <time.h> 88 #include <unistd.h> 89 90 #include <libcasper.h> 91 #include <casper/cap_fileargs.h> 92 #include <casper/cap_syslog.h> 93 94 #include <libxo/xo.h> 95 96 /* The size of the buffer used for I/O. */ 97 #define BUFFERSIZE (1024*1024) 98 99 #define STATUS_BAD 0 100 #define STATUS_GOOD 1 101 #define STATUS_UNKNOWN 2 102 103 static cap_channel_t *capsyslog; 104 static fileargs_t *capfa; 105 static bool checkfor, compress, clear, force, keep; /* flags */ 106 static int verbose; 107 static int nfound, nsaved, nerr; /* statistics */ 108 static int maxdumps; 109 110 extern FILE *zdopen(int, const char *); 111 112 static sig_atomic_t got_siginfo; 113 static void infohandler(int); 114 115 static void 116 logmsg(int pri, const char *fmt, ...) 117 { 118 va_list ap; 119 120 va_start(ap, fmt); 121 if (capsyslog != NULL) 122 cap_vsyslog(capsyslog, pri, fmt, ap); 123 else 124 vsyslog(pri, fmt, ap); 125 va_end(ap); 126 } 127 128 static FILE * 129 xfopenat(int dirfd, const char *path, int flags, const char *modestr, ...) 130 { 131 va_list ap; 132 FILE *fp; 133 mode_t mode; 134 int error, fd; 135 136 if ((flags & O_CREAT) == O_CREAT) { 137 va_start(ap, modestr); 138 mode = (mode_t)va_arg(ap, int); 139 va_end(ap); 140 } else 141 mode = 0; 142 143 fd = openat(dirfd, path, flags, mode); 144 if (fd < 0) 145 return (NULL); 146 fp = fdopen(fd, modestr); 147 if (fp == NULL) { 148 error = errno; 149 (void)close(fd); 150 errno = error; 151 } 152 return (fp); 153 } 154 155 static void 156 printheader(xo_handle_t *xo, const struct kerneldumpheader *h, 157 const char *device, int bounds, const int status) 158 { 159 uint64_t dumplen; 160 time_t t; 161 struct tm tm; 162 char time_str[64]; 163 const char *stat_str; 164 const char *comp_str; 165 166 xo_flush_h(xo); 167 xo_emit_h(xo, "{Lwc:Dump header from device}{:dump_device/%s}\n", 168 device); 169 xo_emit_h(xo, "{P: }{Lwc:Architecture}{:architecture/%s}\n", 170 h->architecture); 171 xo_emit_h(xo, 172 "{P: }{Lwc:Architecture Version}{:architecture_version/%u}\n", 173 dtoh32(h->architectureversion)); 174 dumplen = dtoh64(h->dumplength); 175 xo_emit_h(xo, "{P: }{Lwc:Dump Length}{:dump_length_bytes/%lld}\n", 176 (long long)dumplen); 177 xo_emit_h(xo, "{P: }{Lwc:Blocksize}{:blocksize/%d}\n", 178 dtoh32(h->blocksize)); 179 switch (h->compression) { 180 case KERNELDUMP_COMP_NONE: 181 comp_str = "none"; 182 break; 183 case KERNELDUMP_COMP_GZIP: 184 comp_str = "gzip"; 185 break; 186 case KERNELDUMP_COMP_ZSTD: 187 comp_str = "zstd"; 188 break; 189 default: 190 comp_str = "???"; 191 break; 192 } 193 xo_emit_h(xo, "{P: }{Lwc:Compression}{:compression/%s}\n", comp_str); 194 t = dtoh64(h->dumptime); 195 localtime_r(&t, &tm); 196 if (strftime(time_str, sizeof(time_str), "%F %T %z", &tm) == 0) 197 time_str[0] = '\0'; 198 xo_emit_h(xo, "{P: }{Lwc:Dumptime}{:dumptime/%s}\n", time_str); 199 xo_emit_h(xo, "{P: }{Lwc:Hostname}{:hostname/%s}\n", h->hostname); 200 xo_emit_h(xo, "{P: }{Lwc:Magic}{:magic/%s}\n", h->magic); 201 xo_emit_h(xo, "{P: }{Lwc:Version String}{:version_string/%s}", 202 h->versionstring); 203 xo_emit_h(xo, "{P: }{Lwc:Panic String}{:panic_string/%s}\n", 204 h->panicstring); 205 xo_emit_h(xo, "{P: }{Lwc:Dump Parity}{:dump_parity/%u}\n", h->parity); 206 xo_emit_h(xo, "{P: }{Lwc:Bounds}{:bounds/%d}\n", bounds); 207 208 switch (status) { 209 case STATUS_BAD: 210 stat_str = "bad"; 211 break; 212 case STATUS_GOOD: 213 stat_str = "good"; 214 break; 215 default: 216 stat_str = "unknown"; 217 break; 218 } 219 xo_emit_h(xo, "{P: }{Lwc:Dump Status}{:dump_status/%s}\n", stat_str); 220 xo_flush_h(xo); 221 } 222 223 static int 224 getbounds(int savedirfd) 225 { 226 FILE *fp; 227 char buf[6]; 228 int ret; 229 230 /* 231 * If we are just checking, then we haven't done a chdir to the dump 232 * directory and we should not try to read a bounds file. 233 */ 234 if (checkfor) 235 return (0); 236 237 ret = 0; 238 239 if ((fp = xfopenat(savedirfd, "bounds", O_RDONLY, "r")) == NULL) { 240 if (verbose) 241 printf("unable to open bounds file, using 0\n"); 242 return (ret); 243 } 244 if (fgets(buf, sizeof(buf), fp) == NULL) { 245 if (feof(fp)) 246 logmsg(LOG_WARNING, "bounds file is empty, using 0"); 247 else 248 logmsg(LOG_WARNING, "bounds file: %s", strerror(errno)); 249 fclose(fp); 250 return (ret); 251 } 252 253 errno = 0; 254 ret = (int)strtol(buf, NULL, 10); 255 if (ret == 0 && (errno == EINVAL || errno == ERANGE)) 256 logmsg(LOG_WARNING, "invalid value found in bounds, using 0"); 257 fclose(fp); 258 return (ret); 259 } 260 261 static void 262 writebounds(int savedirfd, int bounds) 263 { 264 FILE *fp; 265 266 if ((fp = xfopenat(savedirfd, "bounds", O_WRONLY | O_CREAT | O_TRUNC, 267 "w", 0644)) == NULL) { 268 logmsg(LOG_WARNING, "unable to write to bounds file: %m"); 269 return; 270 } 271 272 if (verbose) 273 printf("bounds number: %d\n", bounds); 274 275 fprintf(fp, "%d\n", bounds); 276 fclose(fp); 277 } 278 279 static bool 280 writekey(int savedirfd, const char *keyname, uint8_t *dumpkey, 281 uint32_t dumpkeysize) 282 { 283 int fd; 284 285 fd = openat(savedirfd, keyname, O_WRONLY | O_CREAT | O_TRUNC, 0600); 286 if (fd == -1) { 287 logmsg(LOG_ERR, "Unable to open %s to write the key: %m.", 288 keyname); 289 return (false); 290 } 291 292 if (write(fd, dumpkey, dumpkeysize) != (ssize_t)dumpkeysize) { 293 logmsg(LOG_ERR, "Unable to write the key to %s: %m.", keyname); 294 close(fd); 295 return (false); 296 } 297 298 close(fd); 299 return (true); 300 } 301 302 static off_t 303 file_size(int savedirfd, const char *path) 304 { 305 struct stat sb; 306 307 /* Ignore all errors, this file may not exist. */ 308 if (fstatat(savedirfd, path, &sb, 0) == -1) 309 return (0); 310 return (sb.st_size); 311 } 312 313 static off_t 314 saved_dump_size(int savedirfd, int bounds) 315 { 316 static char path[PATH_MAX]; 317 off_t dumpsize; 318 319 dumpsize = 0; 320 321 (void)snprintf(path, sizeof(path), "info.%d", bounds); 322 dumpsize += file_size(savedirfd, path); 323 (void)snprintf(path, sizeof(path), "vmcore.%d", bounds); 324 dumpsize += file_size(savedirfd, path); 325 (void)snprintf(path, sizeof(path), "vmcore.%d.gz", bounds); 326 dumpsize += file_size(savedirfd, path); 327 (void)snprintf(path, sizeof(path), "vmcore.%d.zst", bounds); 328 dumpsize += file_size(savedirfd, path); 329 (void)snprintf(path, sizeof(path), "textdump.tar.%d", bounds); 330 dumpsize += file_size(savedirfd, path); 331 (void)snprintf(path, sizeof(path), "textdump.tar.%d.gz", bounds); 332 dumpsize += file_size(savedirfd, path); 333 334 return (dumpsize); 335 } 336 337 static void 338 saved_dump_remove(int savedirfd, int bounds) 339 { 340 static char path[PATH_MAX]; 341 342 (void)snprintf(path, sizeof(path), "info.%d", bounds); 343 (void)unlinkat(savedirfd, path, 0); 344 (void)snprintf(path, sizeof(path), "vmcore.%d", bounds); 345 (void)unlinkat(savedirfd, path, 0); 346 (void)snprintf(path, sizeof(path), "vmcore.%d.gz", bounds); 347 (void)unlinkat(savedirfd, path, 0); 348 (void)snprintf(path, sizeof(path), "vmcore.%d.zst", bounds); 349 (void)unlinkat(savedirfd, path, 0); 350 (void)snprintf(path, sizeof(path), "textdump.tar.%d", bounds); 351 (void)unlinkat(savedirfd, path, 0); 352 (void)snprintf(path, sizeof(path), "textdump.tar.%d.gz", bounds); 353 (void)unlinkat(savedirfd, path, 0); 354 } 355 356 static void 357 symlinks_remove(int savedirfd) 358 { 359 360 (void)unlinkat(savedirfd, "info.last", 0); 361 (void)unlinkat(savedirfd, "key.last", 0); 362 (void)unlinkat(savedirfd, "vmcore.last", 0); 363 (void)unlinkat(savedirfd, "vmcore.last.gz", 0); 364 (void)unlinkat(savedirfd, "vmcore.last.zst", 0); 365 (void)unlinkat(savedirfd, "vmcore_encrypted.last", 0); 366 (void)unlinkat(savedirfd, "vmcore_encrypted.last.gz", 0); 367 (void)unlinkat(savedirfd, "textdump.tar.last", 0); 368 (void)unlinkat(savedirfd, "textdump.tar.last.gz", 0); 369 } 370 371 /* 372 * Check that sufficient space is available on the disk that holds the 373 * save directory. 374 */ 375 static int 376 check_space(const char *savedir, int savedirfd, off_t dumpsize, int bounds) 377 { 378 char buf[100]; 379 struct statfs fsbuf; 380 FILE *fp; 381 off_t available, minfree, spacefree, totfree, needed; 382 383 if (fstatfs(savedirfd, &fsbuf) < 0) { 384 logmsg(LOG_ERR, "%s: %m", savedir); 385 exit(1); 386 } 387 spacefree = ((off_t) fsbuf.f_bavail * fsbuf.f_bsize) / 1024; 388 totfree = ((off_t) fsbuf.f_bfree * fsbuf.f_bsize) / 1024; 389 390 if ((fp = xfopenat(savedirfd, "minfree", O_RDONLY, "r")) == NULL) 391 minfree = 0; 392 else { 393 if (fgets(buf, sizeof(buf), fp) == NULL) 394 minfree = 0; 395 else { 396 char *endp; 397 398 errno = 0; 399 minfree = strtoll(buf, &endp, 10); 400 if (minfree == 0 && errno != 0) 401 minfree = -1; 402 else { 403 while (*endp != '\0' && isspace(*endp)) 404 endp++; 405 if (*endp != '\0' || minfree < 0) 406 minfree = -1; 407 } 408 if (minfree < 0) 409 logmsg(LOG_WARNING, 410 "`minfree` didn't contain a valid size " 411 "(`%s`). Defaulting to 0", buf); 412 } 413 (void)fclose(fp); 414 } 415 416 available = minfree > 0 ? spacefree - minfree : totfree; 417 needed = dumpsize / 1024 + 2; /* 2 for info file */ 418 needed -= saved_dump_size(savedirfd, bounds); 419 if (available < needed) { 420 logmsg(LOG_WARNING, 421 "no dump: not enough free space on device (need at least " 422 "%jdkB for dump; %jdkB available; %jdkB reserved)", 423 (intmax_t)needed, 424 (intmax_t)available + minfree, 425 (intmax_t)minfree); 426 return (0); 427 } 428 if (spacefree - needed < 0) 429 logmsg(LOG_WARNING, 430 "dump performed, but free space threshold crossed"); 431 return (1); 432 } 433 434 static bool 435 compare_magic(const struct kerneldumpheader *kdh, const char *magic) 436 { 437 438 return (strncmp(kdh->magic, magic, sizeof(kdh->magic)) == 0); 439 } 440 441 #define BLOCKSIZE (1<<12) 442 #define BLOCKMASK (~(BLOCKSIZE-1)) 443 444 static int 445 DoRegularFile(int fd, off_t dumpsize, u_int sectorsize, bool sparse, char *buf, 446 const char *device, const char *filename, FILE *fp) 447 { 448 int he, hs, nr, nw, wl; 449 off_t dmpcnt, origsize; 450 451 dmpcnt = 0; 452 origsize = dumpsize; 453 he = 0; 454 while (dumpsize > 0) { 455 wl = BUFFERSIZE; 456 if (wl > dumpsize) 457 wl = dumpsize; 458 nr = read(fd, buf, roundup(wl, sectorsize)); 459 if (nr != (int)roundup(wl, sectorsize)) { 460 if (nr == 0) 461 logmsg(LOG_WARNING, 462 "WARNING: EOF on dump device"); 463 else 464 logmsg(LOG_ERR, "read error on %s: %m", device); 465 nerr++; 466 return (-1); 467 } 468 if (!sparse) { 469 nw = fwrite(buf, 1, wl, fp); 470 } else { 471 for (nw = 0; nw < nr; nw = he) { 472 /* find a contiguous block of zeroes */ 473 for (hs = nw; hs < nr; hs += BLOCKSIZE) { 474 for (he = hs; he < nr && buf[he] == 0; 475 ++he) 476 /* nothing */ ; 477 /* is the hole long enough to matter? */ 478 if (he >= hs + BLOCKSIZE) 479 break; 480 } 481 482 /* back down to a block boundary */ 483 he &= BLOCKMASK; 484 485 /* 486 * 1) Don't go beyond the end of the buffer. 487 * 2) If the end of the buffer is less than 488 * BLOCKSIZE bytes away, we're at the end 489 * of the file, so just grab what's left. 490 */ 491 if (hs + BLOCKSIZE > nr) 492 hs = he = nr; 493 494 /* 495 * At this point, we have a partial ordering: 496 * nw <= hs <= he <= nr 497 * If hs > nw, buf[nw..hs] contains non-zero 498 * data. If he > hs, buf[hs..he] is all zeroes. 499 */ 500 if (hs > nw) 501 if (fwrite(buf + nw, hs - nw, 1, fp) 502 != 1) 503 break; 504 if (he > hs) 505 if (fseeko(fp, he - hs, SEEK_CUR) == -1) 506 break; 507 } 508 } 509 if (nw != wl) { 510 logmsg(LOG_ERR, 511 "write error on %s file: %m", filename); 512 logmsg(LOG_WARNING, 513 "WARNING: vmcore may be incomplete"); 514 nerr++; 515 return (-1); 516 } 517 if (verbose) { 518 dmpcnt += wl; 519 printf("%llu\r", (unsigned long long)dmpcnt); 520 fflush(stdout); 521 } 522 dumpsize -= wl; 523 if (got_siginfo) { 524 printf("%s %.1lf%%\n", filename, (100.0 - (100.0 * 525 (double)dumpsize / (double)origsize))); 526 got_siginfo = 0; 527 } 528 } 529 return (0); 530 } 531 532 /* 533 * Specialized version of dump-reading logic for use with textdumps, which 534 * are written backwards from the end of the partition, and must be reversed 535 * before being written to the file. Textdumps are small, so do a bit less 536 * work to optimize/sparsify. 537 */ 538 static int 539 DoTextdumpFile(int fd, off_t dumpsize, off_t lasthd, char *buf, 540 const char *device, const char *filename, FILE *fp) 541 { 542 int nr, nw, wl; 543 off_t dmpcnt, totsize; 544 545 totsize = dumpsize; 546 dmpcnt = 0; 547 wl = 512; 548 if ((dumpsize % wl) != 0) { 549 logmsg(LOG_ERR, "textdump uneven multiple of 512 on %s", 550 device); 551 nerr++; 552 return (-1); 553 } 554 while (dumpsize > 0) { 555 nr = pread(fd, buf, wl, lasthd - (totsize - dumpsize) - wl); 556 if (nr != wl) { 557 if (nr == 0) 558 logmsg(LOG_WARNING, 559 "WARNING: EOF on dump device"); 560 else 561 logmsg(LOG_ERR, "read error on %s: %m", device); 562 nerr++; 563 return (-1); 564 } 565 nw = fwrite(buf, 1, wl, fp); 566 if (nw != wl) { 567 logmsg(LOG_ERR, 568 "write error on %s file: %m", filename); 569 logmsg(LOG_WARNING, 570 "WARNING: textdump may be incomplete"); 571 nerr++; 572 return (-1); 573 } 574 if (verbose) { 575 dmpcnt += wl; 576 printf("%llu\r", (unsigned long long)dmpcnt); 577 fflush(stdout); 578 } 579 dumpsize -= wl; 580 } 581 return (0); 582 } 583 584 static void 585 DoFile(const char *savedir, int savedirfd, const char *device) 586 { 587 xo_handle_t *xostdout, *xoinfo; 588 static char infoname[PATH_MAX], corename[PATH_MAX], linkname[PATH_MAX]; 589 static char keyname[PATH_MAX]; 590 static char *buf = NULL; 591 char *temp = NULL; 592 struct kerneldumpheader kdhf, kdhl; 593 uint8_t *dumpkey; 594 off_t mediasize, dumpextent, dumplength, firsthd, lasthd; 595 FILE *core, *info; 596 int fdcore, fddev, error; 597 int bounds, status; 598 u_int sectorsize, xostyle; 599 uint32_t dumpkeysize; 600 bool iscompressed, isencrypted, istextdump, ret; 601 602 bounds = getbounds(savedirfd); 603 dumpkey = NULL; 604 mediasize = 0; 605 status = STATUS_UNKNOWN; 606 607 xostdout = xo_create_to_file(stdout, XO_STYLE_TEXT, 0); 608 if (xostdout == NULL) { 609 logmsg(LOG_ERR, "%s: %m", infoname); 610 return; 611 } 612 613 if (maxdumps > 0 && bounds == maxdumps) 614 bounds = 0; 615 616 if (buf == NULL) { 617 buf = malloc(BUFFERSIZE); 618 if (buf == NULL) { 619 logmsg(LOG_ERR, "%m"); 620 return; 621 } 622 } 623 624 if (verbose) 625 printf("checking for kernel dump on device %s\n", device); 626 627 fddev = fileargs_open(capfa, device); 628 if (fddev < 0) { 629 logmsg(LOG_ERR, "%s: %m", device); 630 return; 631 } 632 633 error = ioctl(fddev, DIOCGMEDIASIZE, &mediasize); 634 if (!error) 635 error = ioctl(fddev, DIOCGSECTORSIZE, §orsize); 636 if (error) { 637 logmsg(LOG_ERR, 638 "couldn't find media and/or sector size of %s: %m", device); 639 goto closefd; 640 } 641 642 if (verbose) { 643 printf("mediasize = %lld bytes\n", (long long)mediasize); 644 printf("sectorsize = %u bytes\n", sectorsize); 645 } 646 647 if (sectorsize < sizeof(kdhl)) { 648 logmsg(LOG_ERR, 649 "Sector size is less the kernel dump header %zu", 650 sizeof(kdhl)); 651 goto closefd; 652 } 653 654 lasthd = mediasize - sectorsize; 655 temp = malloc(sectorsize); 656 if (temp == NULL) { 657 logmsg(LOG_ERR, "%m"); 658 goto closefd; 659 } 660 if (lseek(fddev, lasthd, SEEK_SET) != lasthd || 661 read(fddev, temp, sectorsize) != (ssize_t)sectorsize) { 662 logmsg(LOG_ERR, 663 "error reading last dump header at offset %lld in %s: %m", 664 (long long)lasthd, device); 665 goto closefd; 666 } 667 memcpy(&kdhl, temp, sizeof(kdhl)); 668 iscompressed = istextdump = false; 669 if (compare_magic(&kdhl, TEXTDUMPMAGIC)) { 670 if (verbose) 671 printf("textdump magic on last dump header on %s\n", 672 device); 673 istextdump = true; 674 if (dtoh32(kdhl.version) != KERNELDUMP_TEXT_VERSION) { 675 logmsg(LOG_ERR, 676 "unknown version (%d) in last dump header on %s", 677 dtoh32(kdhl.version), device); 678 679 status = STATUS_BAD; 680 if (force == false) 681 goto closefd; 682 } 683 } else if (compare_magic(&kdhl, KERNELDUMPMAGIC)) { 684 if (dtoh32(kdhl.version) != KERNELDUMPVERSION) { 685 logmsg(LOG_ERR, 686 "unknown version (%d) in last dump header on %s", 687 dtoh32(kdhl.version), device); 688 689 status = STATUS_BAD; 690 if (force == false) 691 goto closefd; 692 } 693 switch (kdhl.compression) { 694 case KERNELDUMP_COMP_NONE: 695 break; 696 case KERNELDUMP_COMP_GZIP: 697 case KERNELDUMP_COMP_ZSTD: 698 if (compress && verbose) 699 printf("dump is already compressed\n"); 700 compress = false; 701 iscompressed = true; 702 break; 703 default: 704 logmsg(LOG_ERR, "unknown compression type %d on %s", 705 kdhl.compression, device); 706 break; 707 } 708 } else { 709 if (verbose) 710 printf("magic mismatch on last dump header on %s\n", 711 device); 712 713 status = STATUS_BAD; 714 if (force == false) 715 goto closefd; 716 717 if (compare_magic(&kdhl, KERNELDUMPMAGIC_CLEARED)) { 718 if (verbose) 719 printf("forcing magic on %s\n", device); 720 memcpy(kdhl.magic, KERNELDUMPMAGIC, sizeof(kdhl.magic)); 721 } else { 722 logmsg(LOG_ERR, "unable to force dump - bad magic"); 723 goto closefd; 724 } 725 if (dtoh32(kdhl.version) != KERNELDUMPVERSION) { 726 logmsg(LOG_ERR, 727 "unknown version (%d) in last dump header on %s", 728 dtoh32(kdhl.version), device); 729 730 status = STATUS_BAD; 731 if (force == false) 732 goto closefd; 733 } 734 } 735 736 nfound++; 737 if (clear) 738 goto nuke; 739 740 if (kerneldump_parity(&kdhl)) { 741 logmsg(LOG_ERR, 742 "parity error on last dump header on %s", device); 743 nerr++; 744 status = STATUS_BAD; 745 if (force == false) 746 goto closefd; 747 } 748 dumpextent = dtoh64(kdhl.dumpextent); 749 dumplength = dtoh64(kdhl.dumplength); 750 dumpkeysize = dtoh32(kdhl.dumpkeysize); 751 firsthd = lasthd - dumpextent - sectorsize - dumpkeysize; 752 if (lseek(fddev, firsthd, SEEK_SET) != firsthd || 753 read(fddev, temp, sectorsize) != (ssize_t)sectorsize) { 754 logmsg(LOG_ERR, 755 "error reading first dump header at offset %lld in %s: %m", 756 (long long)firsthd, device); 757 nerr++; 758 goto closefd; 759 } 760 memcpy(&kdhf, temp, sizeof(kdhf)); 761 762 if (verbose >= 2) { 763 printf("First dump headers:\n"); 764 printheader(xostdout, &kdhf, device, bounds, -1); 765 766 printf("\nLast dump headers:\n"); 767 printheader(xostdout, &kdhl, device, bounds, -1); 768 printf("\n"); 769 } 770 771 if (memcmp(&kdhl, &kdhf, sizeof(kdhl))) { 772 logmsg(LOG_ERR, 773 "first and last dump headers disagree on %s", device); 774 nerr++; 775 status = STATUS_BAD; 776 if (force == false) 777 goto closefd; 778 } else { 779 status = STATUS_GOOD; 780 } 781 782 if (checkfor) { 783 printf("A dump exists on %s\n", device); 784 close(fddev); 785 exit(0); 786 } 787 788 if (kdhl.panicstring[0] != '\0') 789 logmsg(LOG_ALERT, "reboot after panic: %.*s", 790 (int)sizeof(kdhl.panicstring), kdhl.panicstring); 791 else 792 logmsg(LOG_ALERT, "reboot"); 793 794 if (verbose) 795 printf("Checking for available free space\n"); 796 797 if (!check_space(savedir, savedirfd, dumplength, bounds)) { 798 nerr++; 799 goto closefd; 800 } 801 802 writebounds(savedirfd, bounds + 1); 803 804 saved_dump_remove(savedirfd, bounds); 805 806 snprintf(infoname, sizeof(infoname), "info.%d", bounds); 807 808 /* 809 * Create or overwrite any existing dump header files. 810 */ 811 if ((info = xfopenat(savedirfd, infoname, 812 O_WRONLY | O_CREAT | O_TRUNC, "w", 0600)) == NULL) { 813 logmsg(LOG_ERR, "open(%s): %m", infoname); 814 nerr++; 815 goto closefd; 816 } 817 818 isencrypted = (dumpkeysize > 0); 819 if (compress) 820 snprintf(corename, sizeof(corename), "%s.%d.gz", 821 istextdump ? "textdump.tar" : 822 (isencrypted ? "vmcore_encrypted" : "vmcore"), bounds); 823 else if (iscompressed && !isencrypted) 824 snprintf(corename, sizeof(corename), "vmcore.%d.%s", bounds, 825 (kdhl.compression == KERNELDUMP_COMP_GZIP) ? "gz" : "zst"); 826 else 827 snprintf(corename, sizeof(corename), "%s.%d", 828 istextdump ? "textdump.tar" : 829 (isencrypted ? "vmcore_encrypted" : "vmcore"), bounds); 830 fdcore = openat(savedirfd, corename, O_WRONLY | O_CREAT | O_TRUNC, 831 0600); 832 if (fdcore < 0) { 833 logmsg(LOG_ERR, "open(%s): %m", corename); 834 fclose(info); 835 nerr++; 836 goto closefd; 837 } 838 839 if (compress) 840 core = zdopen(fdcore, "w"); 841 else 842 core = fdopen(fdcore, "w"); 843 if (core == NULL) { 844 logmsg(LOG_ERR, "%s: %m", corename); 845 (void)close(fdcore); 846 (void)fclose(info); 847 nerr++; 848 goto closefd; 849 } 850 fdcore = -1; 851 852 xostyle = xo_get_style(NULL); 853 xoinfo = xo_create_to_file(info, xostyle, 0); 854 if (xoinfo == NULL) { 855 logmsg(LOG_ERR, "%s: %m", infoname); 856 fclose(info); 857 nerr++; 858 goto closeall; 859 } 860 xo_open_container_h(xoinfo, "crashdump"); 861 862 if (verbose) 863 printheader(xostdout, &kdhl, device, bounds, status); 864 865 printheader(xoinfo, &kdhl, device, bounds, status); 866 xo_close_container_h(xoinfo, "crashdump"); 867 xo_flush_h(xoinfo); 868 xo_finish_h(xoinfo); 869 fclose(info); 870 871 if (isencrypted) { 872 dumpkey = calloc(1, dumpkeysize); 873 if (dumpkey == NULL) { 874 logmsg(LOG_ERR, "Unable to allocate kernel dump key."); 875 nerr++; 876 goto closeall; 877 } 878 879 if (read(fddev, dumpkey, dumpkeysize) != (ssize_t)dumpkeysize) { 880 logmsg(LOG_ERR, "Unable to read kernel dump key: %m."); 881 nerr++; 882 goto closeall; 883 } 884 885 snprintf(keyname, sizeof(keyname), "key.%d", bounds); 886 ret = writekey(savedirfd, keyname, dumpkey, dumpkeysize); 887 explicit_bzero(dumpkey, dumpkeysize); 888 if (!ret) { 889 nerr++; 890 goto closeall; 891 } 892 } 893 894 logmsg(LOG_NOTICE, "writing %s%score to %s/%s", 895 isencrypted ? "encrypted " : "", compress ? "compressed " : "", 896 savedir, corename); 897 898 if (istextdump) { 899 if (DoTextdumpFile(fddev, dumplength, lasthd, buf, device, 900 corename, core) < 0) 901 goto closeall; 902 } else { 903 if (DoRegularFile(fddev, dumplength, sectorsize, 904 !(compress || iscompressed || isencrypted), buf, device, 905 corename, core) < 0) { 906 goto closeall; 907 } 908 } 909 if (verbose) 910 printf("\n"); 911 912 if (fclose(core) < 0) { 913 logmsg(LOG_ERR, "error on %s: %m", corename); 914 nerr++; 915 goto closefd; 916 } 917 918 symlinks_remove(savedirfd); 919 if (symlinkat(infoname, savedirfd, "info.last") == -1) { 920 logmsg(LOG_WARNING, "unable to create symlink %s/%s: %m", 921 savedir, "info.last"); 922 } 923 if (isencrypted) { 924 if (symlinkat(keyname, savedirfd, "key.last") == -1) { 925 logmsg(LOG_WARNING, 926 "unable to create symlink %s/%s: %m", savedir, 927 "key.last"); 928 } 929 } 930 if (compress || iscompressed) { 931 snprintf(linkname, sizeof(linkname), "%s.last.%s", 932 istextdump ? "textdump.tar" : 933 (isencrypted ? "vmcore_encrypted" : "vmcore"), 934 (kdhl.compression == KERNELDUMP_COMP_ZSTD) ? "zst" : "gz"); 935 } else { 936 snprintf(linkname, sizeof(linkname), "%s.last", 937 istextdump ? "textdump.tar" : 938 (isencrypted ? "vmcore_encrypted" : "vmcore")); 939 } 940 if (symlinkat(corename, savedirfd, linkname) == -1) { 941 logmsg(LOG_WARNING, "unable to create symlink %s/%s: %m", 942 savedir, linkname); 943 } 944 945 nsaved++; 946 947 if (verbose) 948 printf("dump saved\n"); 949 950 nuke: 951 if (!keep) { 952 if (verbose) 953 printf("clearing dump header\n"); 954 memcpy(kdhl.magic, KERNELDUMPMAGIC_CLEARED, sizeof(kdhl.magic)); 955 memcpy(temp, &kdhl, sizeof(kdhl)); 956 if (lseek(fddev, lasthd, SEEK_SET) != lasthd || 957 write(fddev, temp, sectorsize) != (ssize_t)sectorsize) 958 logmsg(LOG_ERR, 959 "error while clearing the dump header: %m"); 960 } 961 xo_close_container_h(xostdout, "crashdump"); 962 xo_finish_h(xostdout); 963 free(dumpkey); 964 free(temp); 965 close(fddev); 966 return; 967 968 closeall: 969 fclose(core); 970 971 closefd: 972 free(dumpkey); 973 free(temp); 974 close(fddev); 975 } 976 977 /* Prepend "/dev/" to any arguments that don't already have it */ 978 static char ** 979 devify(int argc, char **argv) 980 { 981 char **devs; 982 int i, l; 983 984 devs = malloc(argc * sizeof(*argv)); 985 if (devs == NULL) { 986 logmsg(LOG_ERR, "malloc(): %m"); 987 exit(1); 988 } 989 for (i = 0; i < argc; i++) { 990 if (strncmp(argv[i], _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0) 991 devs[i] = strdup(argv[i]); 992 else { 993 char *fullpath; 994 995 fullpath = malloc(PATH_MAX); 996 if (fullpath == NULL) { 997 logmsg(LOG_ERR, "malloc(): %m"); 998 exit(1); 999 } 1000 l = snprintf(fullpath, PATH_MAX, "%s%s", _PATH_DEV, 1001 argv[i]); 1002 if (l < 0) { 1003 logmsg(LOG_ERR, "snprintf(): %m"); 1004 exit(1); 1005 } else if (l >= PATH_MAX) { 1006 logmsg(LOG_ERR, "device name too long"); 1007 exit(1); 1008 } 1009 devs[i] = fullpath; 1010 } 1011 } 1012 return (devs); 1013 } 1014 1015 static char ** 1016 enum_dumpdevs(int *argcp) 1017 { 1018 struct fstab *fsp; 1019 char **argv; 1020 int argc, n; 1021 1022 /* 1023 * We cannot use getfsent(3) in capability mode, so we must 1024 * scan /etc/fstab and build up a list of candidate devices 1025 * before proceeding. 1026 */ 1027 argc = 0; 1028 n = 8; 1029 argv = malloc(n * sizeof(*argv)); 1030 if (argv == NULL) { 1031 logmsg(LOG_ERR, "malloc(): %m"); 1032 exit(1); 1033 } 1034 for (;;) { 1035 fsp = getfsent(); 1036 if (fsp == NULL) 1037 break; 1038 if (strcmp(fsp->fs_vfstype, "swap") != 0 && 1039 strcmp(fsp->fs_vfstype, "dump") != 0) 1040 continue; 1041 if (argc >= n) { 1042 n *= 2; 1043 argv = realloc(argv, n * sizeof(*argv)); 1044 if (argv == NULL) { 1045 logmsg(LOG_ERR, "realloc(): %m"); 1046 exit(1); 1047 } 1048 } 1049 argv[argc] = strdup(fsp->fs_spec); 1050 if (argv[argc] == NULL) { 1051 logmsg(LOG_ERR, "strdup(): %m"); 1052 exit(1); 1053 } 1054 argc++; 1055 } 1056 *argcp = argc; 1057 return (argv); 1058 } 1059 1060 static void 1061 init_caps(int argc, char **argv) 1062 { 1063 cap_rights_t rights; 1064 cap_channel_t *capcas; 1065 1066 capcas = cap_init(); 1067 if (capcas == NULL) { 1068 logmsg(LOG_ERR, "cap_init(): %m"); 1069 exit(1); 1070 } 1071 /* 1072 * The fileargs capability does not currently provide a way to limit 1073 * ioctls. 1074 */ 1075 (void)cap_rights_init(&rights, CAP_PREAD, CAP_WRITE, CAP_IOCTL); 1076 capfa = fileargs_init(argc, argv, checkfor || keep ? O_RDONLY : O_RDWR, 1077 0, &rights, FA_OPEN); 1078 if (capfa == NULL) { 1079 logmsg(LOG_ERR, "fileargs_init(): %m"); 1080 exit(1); 1081 } 1082 caph_cache_catpages(); 1083 caph_cache_tzdata(); 1084 if (caph_enter_casper() != 0) { 1085 logmsg(LOG_ERR, "caph_enter_casper(): %m"); 1086 exit(1); 1087 } 1088 capsyslog = cap_service_open(capcas, "system.syslog"); 1089 if (capsyslog == NULL) { 1090 logmsg(LOG_ERR, "cap_service_open(system.syslog): %m"); 1091 exit(1); 1092 } 1093 cap_close(capcas); 1094 } 1095 1096 static void 1097 usage(void) 1098 { 1099 xo_error("%s\n%s\n%s\n", 1100 "usage: savecore -c [-v] [device ...]", 1101 " savecore -C [-v] [device ...]", 1102 " savecore [-fkvz] [-m maxdumps] [directory [device ...]]"); 1103 exit(1); 1104 } 1105 1106 int 1107 main(int argc, char **argv) 1108 { 1109 cap_rights_t rights; 1110 const char *savedir; 1111 char **devs; 1112 int i, ch, error, savedirfd; 1113 1114 checkfor = compress = clear = force = keep = false; 1115 verbose = 0; 1116 nfound = nsaved = nerr = 0; 1117 savedir = "."; 1118 1119 openlog("savecore", LOG_PERROR, LOG_DAEMON); 1120 signal(SIGINFO, infohandler); 1121 1122 argc = xo_parse_args(argc, argv); 1123 if (argc < 0) 1124 exit(1); 1125 1126 while ((ch = getopt(argc, argv, "Ccfkm:vz")) != -1) 1127 switch(ch) { 1128 case 'C': 1129 checkfor = true; 1130 break; 1131 case 'c': 1132 clear = true; 1133 break; 1134 case 'f': 1135 force = true; 1136 break; 1137 case 'k': 1138 keep = true; 1139 break; 1140 case 'm': 1141 maxdumps = atoi(optarg); 1142 if (maxdumps <= 0) { 1143 logmsg(LOG_ERR, "Invalid maxdump value"); 1144 exit(1); 1145 } 1146 break; 1147 case 'v': 1148 verbose++; 1149 break; 1150 case 'z': 1151 compress = 1; 1152 break; 1153 case '?': 1154 default: 1155 usage(); 1156 } 1157 if (checkfor && (clear || force || keep)) 1158 usage(); 1159 if (clear && (compress || keep)) 1160 usage(); 1161 if (maxdumps > 0 && (checkfor || clear)) 1162 usage(); 1163 argc -= optind; 1164 argv += optind; 1165 if (argc >= 1 && !checkfor && !clear) { 1166 error = chdir(argv[0]); 1167 if (error) { 1168 logmsg(LOG_ERR, "chdir(%s): %m", argv[0]); 1169 exit(1); 1170 } 1171 savedir = argv[0]; 1172 argc--; 1173 argv++; 1174 } 1175 if (argc == 0) 1176 devs = enum_dumpdevs(&argc); 1177 else 1178 devs = devify(argc, argv); 1179 1180 savedirfd = open(savedir, O_RDONLY | O_DIRECTORY); 1181 if (savedirfd < 0) { 1182 logmsg(LOG_ERR, "open(%s): %m", savedir); 1183 exit(1); 1184 } 1185 (void)cap_rights_init(&rights, CAP_CREATE, CAP_FCNTL, CAP_FSTATAT, 1186 CAP_FSTATFS, CAP_PREAD, CAP_SYMLINKAT, CAP_FTRUNCATE, CAP_UNLINKAT, 1187 CAP_WRITE); 1188 if (caph_rights_limit(savedirfd, &rights) < 0) { 1189 logmsg(LOG_ERR, "cap_rights_limit(): %m"); 1190 exit(1); 1191 } 1192 1193 /* Enter capability mode. */ 1194 init_caps(argc, devs); 1195 1196 for (i = 0; i < argc; i++) 1197 DoFile(savedir, savedirfd, devs[i]); 1198 1199 /* Emit minimal output. */ 1200 if (nfound == 0) { 1201 if (checkfor) { 1202 if (verbose) 1203 printf("No dump exists\n"); 1204 exit(1); 1205 } 1206 if (verbose) 1207 logmsg(LOG_WARNING, "no dumps found"); 1208 } else if (nsaved == 0) { 1209 if (nerr != 0) { 1210 if (verbose) 1211 logmsg(LOG_WARNING, 1212 "unsaved dumps found but not saved"); 1213 exit(1); 1214 } else if (verbose) 1215 logmsg(LOG_WARNING, "no unsaved dumps found"); 1216 } 1217 1218 return (0); 1219 } 1220 1221 static void 1222 infohandler(int sig __unused) 1223 { 1224 got_siginfo = 1; 1225 } 1226