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