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