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