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 ssize_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, w; 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 w = sparsefwrite(zbuf, zbufsize - z->avail_out, fp); 524 if (w < zbufsize - z->avail_out) 525 return (-1); 526 nw += w; 527 } while (z->avail_in > 0 && rv != Z_STREAM_END); 528 529 return (nw); 530 } 531 532 static ssize_t 533 ZstdWrite(ZSTD_DCtx *Zctx, char *in, size_t insize, FILE *fp) 534 { 535 ZSTD_inBuffer Zin; 536 ZSTD_outBuffer Zout; 537 size_t nw = 0, w; 538 int rv; 539 540 Zin.src = in; 541 Zin.size = insize; 542 Zin.pos = 0; 543 do { 544 Zout.dst = zbuf; 545 Zout.size = zbufsize; 546 Zout.pos = 0; 547 rv = ZSTD_decompressStream(Zctx, &Zout, &Zin); 548 if (ZSTD_isError(rv)) { 549 logmsg(LOG_ERR, "decompression failed: %s", 550 ZSTD_getErrorName(rv)); 551 return (-1); 552 } 553 w = sparsefwrite(zbuf, Zout.pos, fp); 554 if (w < Zout.pos) 555 return (-1); 556 nw += w; 557 } while (Zin.pos < Zin.size && rv != 0); 558 559 return (nw); 560 } 561 562 static int 563 DoRegularFile(int fd, off_t dumpsize, u_int sectorsize, bool sparse, 564 uint8_t compression, char *buf, const char *device, 565 const char *filename, FILE *fp) 566 { 567 size_t nr, wl; 568 ssize_t nw; 569 off_t dmpcnt, origsize; 570 z_stream z; /* gzip */ 571 ZSTD_DCtx *Zctx; /* zstd */ 572 573 dmpcnt = 0; 574 origsize = dumpsize; 575 if (compression == KERNELDUMP_COMP_GZIP) { 576 memset(&z, 0, sizeof(z)); 577 z.zalloc = Z_NULL; 578 z.zfree = Z_NULL; 579 if (inflateInit2(&z, -MAX_WBITS) != Z_OK) { 580 logmsg(LOG_ERR, "failed to initialize zlib: %s", z.msg); 581 return (-1); 582 } 583 zbufsize = BUFFERSIZE; 584 } else if (compression == KERNELDUMP_COMP_ZSTD) { 585 if ((Zctx = ZSTD_createDCtx()) == NULL) { 586 logmsg(LOG_ERR, "failed to initialize zstd"); 587 return (-1); 588 } 589 zbufsize = ZSTD_DStreamOutSize(); 590 } 591 if (zbufsize > 0) 592 if ((zbuf = malloc(zbufsize)) == NULL) { 593 logmsg(LOG_ERR, "failed to alloc decompression buffer"); 594 return (-1); 595 } 596 597 while (dumpsize > 0) { 598 wl = BUFFERSIZE; 599 if (wl > (size_t)dumpsize) 600 wl = dumpsize; 601 nr = read(fd, buf, roundup(wl, sectorsize)); 602 if (nr != roundup(wl, sectorsize)) { 603 if (nr == 0) 604 logmsg(LOG_WARNING, 605 "WARNING: EOF on dump device"); 606 else 607 logmsg(LOG_ERR, "read error on %s: %m", device); 608 nerr++; 609 return (-1); 610 } 611 if (compression == KERNELDUMP_COMP_GZIP) 612 nw = GunzipWrite(&z, buf, nr, fp); 613 else if (compression == KERNELDUMP_COMP_ZSTD) 614 nw = ZstdWrite(Zctx, buf, nr, fp); 615 else if (!sparse) 616 nw = fwrite(buf, 1, wl, fp); 617 else 618 nw = sparsefwrite(buf, wl, fp); 619 if (nw < 0 || (compression == KERNELDUMP_COMP_NONE && 620 (size_t)nw != wl)) { 621 logmsg(LOG_ERR, 622 "write error on %s file: %m", filename); 623 logmsg(LOG_WARNING, 624 "WARNING: vmcore may be incomplete"); 625 nerr++; 626 return (-1); 627 } 628 if (verbose) { 629 dmpcnt += wl; 630 printf("%llu\r", (unsigned long long)dmpcnt); 631 fflush(stdout); 632 } 633 dumpsize -= wl; 634 if (got_siginfo) { 635 printf("%s %.1lf%%\n", filename, (100.0 - (100.0 * 636 (double)dumpsize / (double)origsize))); 637 got_siginfo = 0; 638 } 639 } 640 return (0); 641 } 642 643 /* 644 * Specialized version of dump-reading logic for use with textdumps, which 645 * are written backwards from the end of the partition, and must be reversed 646 * before being written to the file. Textdumps are small, so do a bit less 647 * work to optimize/sparsify. 648 */ 649 static int 650 DoTextdumpFile(int fd, off_t dumpsize, off_t lasthd, char *buf, 651 const char *device, const char *filename, FILE *fp) 652 { 653 int nr, nw, wl; 654 off_t dmpcnt, totsize; 655 656 totsize = dumpsize; 657 dmpcnt = 0; 658 wl = 512; 659 if ((dumpsize % wl) != 0) { 660 logmsg(LOG_ERR, "textdump uneven multiple of 512 on %s", 661 device); 662 nerr++; 663 return (-1); 664 } 665 while (dumpsize > 0) { 666 nr = pread(fd, buf, wl, lasthd - (totsize - dumpsize) - wl); 667 if (nr != wl) { 668 if (nr == 0) 669 logmsg(LOG_WARNING, 670 "WARNING: EOF on dump device"); 671 else 672 logmsg(LOG_ERR, "read error on %s: %m", device); 673 nerr++; 674 return (-1); 675 } 676 nw = fwrite(buf, 1, wl, fp); 677 if (nw != wl) { 678 logmsg(LOG_ERR, 679 "write error on %s file: %m", filename); 680 logmsg(LOG_WARNING, 681 "WARNING: textdump may be incomplete"); 682 nerr++; 683 return (-1); 684 } 685 if (verbose) { 686 dmpcnt += wl; 687 printf("%llu\r", (unsigned long long)dmpcnt); 688 fflush(stdout); 689 } 690 dumpsize -= wl; 691 } 692 return (0); 693 } 694 695 static void 696 DoFile(const char *savedir, int savedirfd, const char *device) 697 { 698 xo_handle_t *xostdout, *xoinfo; 699 static char infoname[PATH_MAX], corename[PATH_MAX], linkname[PATH_MAX]; 700 static char keyname[PATH_MAX]; 701 static char *buf = NULL; 702 char *temp = NULL; 703 struct kerneldumpheader kdhf, kdhl; 704 uint8_t *dumpkey; 705 off_t mediasize, dumpextent, dumplength, firsthd, lasthd; 706 FILE *core, *info; 707 int fdcore, fddev, error; 708 int bounds, status; 709 u_int sectorsize, xostyle; 710 uint32_t dumpkeysize; 711 bool iscompressed, isencrypted, istextdump, ret; 712 713 bounds = getbounds(savedirfd); 714 dumpkey = NULL; 715 mediasize = 0; 716 status = STATUS_UNKNOWN; 717 718 xostdout = xo_create_to_file(stdout, XO_STYLE_TEXT, 0); 719 if (xostdout == NULL) { 720 logmsg(LOG_ERR, "%s: %m", infoname); 721 return; 722 } 723 724 if (maxdumps > 0 && bounds == maxdumps) 725 bounds = 0; 726 727 if (buf == NULL) { 728 buf = malloc(BUFFERSIZE); 729 if (buf == NULL) { 730 logmsg(LOG_ERR, "%m"); 731 return; 732 } 733 } 734 735 if (verbose) 736 printf("checking for kernel dump on device %s\n", device); 737 738 fddev = fileargs_open(capfa, device); 739 if (fddev < 0) { 740 logmsg(LOG_ERR, "%s: %m", device); 741 return; 742 } 743 744 error = ioctl(fddev, DIOCGMEDIASIZE, &mediasize); 745 if (!error) 746 error = ioctl(fddev, DIOCGSECTORSIZE, §orsize); 747 if (error) { 748 logmsg(LOG_ERR, 749 "couldn't find media and/or sector size of %s: %m", device); 750 goto closefd; 751 } 752 753 if (verbose) { 754 printf("mediasize = %lld bytes\n", (long long)mediasize); 755 printf("sectorsize = %u bytes\n", sectorsize); 756 } 757 758 if (sectorsize < sizeof(kdhl)) { 759 logmsg(LOG_ERR, 760 "Sector size is less the kernel dump header %zu", 761 sizeof(kdhl)); 762 goto closefd; 763 } 764 765 lasthd = mediasize - sectorsize; 766 temp = malloc(sectorsize); 767 if (temp == NULL) { 768 logmsg(LOG_ERR, "%m"); 769 goto closefd; 770 } 771 if (lseek(fddev, lasthd, SEEK_SET) != lasthd || 772 read(fddev, temp, sectorsize) != (ssize_t)sectorsize) { 773 logmsg(LOG_ERR, 774 "error reading last dump header at offset %lld in %s: %m", 775 (long long)lasthd, device); 776 goto closefd; 777 } 778 memcpy(&kdhl, temp, sizeof(kdhl)); 779 iscompressed = istextdump = false; 780 if (compare_magic(&kdhl, TEXTDUMPMAGIC)) { 781 if (verbose) 782 printf("textdump magic on last dump header on %s\n", 783 device); 784 istextdump = true; 785 if (dtoh32(kdhl.version) != KERNELDUMP_TEXT_VERSION) { 786 logmsg(LOG_ERR, 787 "unknown version (%d) in last dump header on %s", 788 dtoh32(kdhl.version), device); 789 790 status = STATUS_BAD; 791 if (!force) 792 goto closefd; 793 } 794 } else if (compare_magic(&kdhl, KERNELDUMPMAGIC)) { 795 if (dtoh32(kdhl.version) != KERNELDUMPVERSION) { 796 logmsg(LOG_ERR, 797 "unknown version (%d) in last dump header on %s", 798 dtoh32(kdhl.version), device); 799 800 status = STATUS_BAD; 801 if (!force) 802 goto closefd; 803 } 804 switch (kdhl.compression) { 805 case KERNELDUMP_COMP_NONE: 806 uncompress = false; 807 break; 808 case KERNELDUMP_COMP_GZIP: 809 case KERNELDUMP_COMP_ZSTD: 810 if (compress && verbose) 811 printf("dump is already compressed\n"); 812 if (uncompress && verbose) 813 printf("dump to be uncompressed\n"); 814 compress = false; 815 iscompressed = true; 816 break; 817 default: 818 logmsg(LOG_ERR, "unknown compression type %d on %s", 819 kdhl.compression, device); 820 break; 821 } 822 } else { 823 if (verbose) 824 printf("magic mismatch on last dump header on %s\n", 825 device); 826 827 status = STATUS_BAD; 828 if (!force) 829 goto closefd; 830 831 if (compare_magic(&kdhl, KERNELDUMPMAGIC_CLEARED)) { 832 if (verbose) 833 printf("forcing magic on %s\n", device); 834 memcpy(kdhl.magic, KERNELDUMPMAGIC, sizeof(kdhl.magic)); 835 } else { 836 logmsg(LOG_ERR, "unable to force dump - bad magic"); 837 goto closefd; 838 } 839 if (dtoh32(kdhl.version) != KERNELDUMPVERSION) { 840 logmsg(LOG_ERR, 841 "unknown version (%d) in last dump header on %s", 842 dtoh32(kdhl.version), device); 843 844 status = STATUS_BAD; 845 if (!force) 846 goto closefd; 847 } 848 } 849 850 nfound++; 851 if (clear) 852 goto nuke; 853 854 if (kerneldump_parity(&kdhl)) { 855 logmsg(LOG_ERR, 856 "parity error on last dump header on %s", device); 857 nerr++; 858 status = STATUS_BAD; 859 if (!force) 860 goto closefd; 861 } 862 dumpextent = dtoh64(kdhl.dumpextent); 863 dumplength = dtoh64(kdhl.dumplength); 864 dumpkeysize = dtoh32(kdhl.dumpkeysize); 865 firsthd = lasthd - dumpextent - sectorsize - dumpkeysize; 866 if (lseek(fddev, firsthd, SEEK_SET) != firsthd || 867 read(fddev, temp, sectorsize) != (ssize_t)sectorsize) { 868 logmsg(LOG_ERR, 869 "error reading first dump header at offset %lld in %s: %m", 870 (long long)firsthd, device); 871 nerr++; 872 goto closefd; 873 } 874 memcpy(&kdhf, temp, sizeof(kdhf)); 875 876 if (verbose >= 2) { 877 printf("First dump headers:\n"); 878 printheader(xostdout, &kdhf, device, bounds, -1); 879 880 printf("\nLast dump headers:\n"); 881 printheader(xostdout, &kdhl, device, bounds, -1); 882 printf("\n"); 883 } 884 885 if (memcmp(&kdhl, &kdhf, sizeof(kdhl))) { 886 logmsg(LOG_ERR, 887 "first and last dump headers disagree on %s", device); 888 nerr++; 889 status = STATUS_BAD; 890 if (!force) 891 goto closefd; 892 } else { 893 status = STATUS_GOOD; 894 } 895 896 if (checkfor) { 897 printf("A dump exists on %s\n", device); 898 close(fddev); 899 exit(0); 900 } 901 902 if (kdhl.panicstring[0] != '\0') 903 logmsg(LOG_ALERT, "reboot after panic: %.*s", 904 (int)sizeof(kdhl.panicstring), kdhl.panicstring); 905 else 906 logmsg(LOG_ALERT, "reboot"); 907 908 if (verbose) 909 printf("Checking for available free space\n"); 910 911 if (!check_space(savedir, savedirfd, dumplength, bounds)) { 912 nerr++; 913 goto closefd; 914 } 915 916 writebounds(savedirfd, bounds + 1); 917 918 saved_dump_remove(savedirfd, bounds); 919 920 snprintf(infoname, sizeof(infoname), "info.%d", bounds); 921 922 /* 923 * Create or overwrite any existing dump header files. 924 */ 925 if ((info = xfopenat(savedirfd, infoname, 926 O_WRONLY | O_CREAT | O_TRUNC, "w", 0600)) == NULL) { 927 logmsg(LOG_ERR, "open(%s): %m", infoname); 928 nerr++; 929 goto closefd; 930 } 931 932 isencrypted = (dumpkeysize > 0); 933 if (compress) 934 snprintf(corename, sizeof(corename), "%s.%d.gz", 935 istextdump ? "textdump.tar" : 936 (isencrypted ? "vmcore_encrypted" : "vmcore"), bounds); 937 else if (iscompressed && !isencrypted && !uncompress) 938 snprintf(corename, sizeof(corename), "vmcore.%d.%s", bounds, 939 (kdhl.compression == KERNELDUMP_COMP_GZIP) ? "gz" : "zst"); 940 else 941 snprintf(corename, sizeof(corename), "%s.%d", 942 istextdump ? "textdump.tar" : 943 (isencrypted ? "vmcore_encrypted" : "vmcore"), bounds); 944 fdcore = openat(savedirfd, corename, O_WRONLY | O_CREAT | O_TRUNC, 945 0600); 946 if (fdcore < 0) { 947 logmsg(LOG_ERR, "open(%s): %m", corename); 948 fclose(info); 949 nerr++; 950 goto closefd; 951 } 952 953 if (compress) 954 core = zdopen(fdcore, "w"); 955 else 956 core = fdopen(fdcore, "w"); 957 if (core == NULL) { 958 logmsg(LOG_ERR, "%s: %m", corename); 959 (void)close(fdcore); 960 (void)fclose(info); 961 nerr++; 962 goto closefd; 963 } 964 fdcore = -1; 965 966 xostyle = xo_get_style(NULL); 967 xoinfo = xo_create_to_file(info, xostyle, 0); 968 if (xoinfo == NULL) { 969 logmsg(LOG_ERR, "%s: %m", infoname); 970 fclose(info); 971 nerr++; 972 goto closeall; 973 } 974 xo_open_container_h(xoinfo, "crashdump"); 975 976 if (verbose) 977 printheader(xostdout, &kdhl, device, bounds, status); 978 979 printheader(xoinfo, &kdhl, device, bounds, status); 980 xo_close_container_h(xoinfo, "crashdump"); 981 xo_flush_h(xoinfo); 982 xo_finish_h(xoinfo); 983 fclose(info); 984 985 if (isencrypted) { 986 dumpkey = calloc(1, dumpkeysize); 987 if (dumpkey == NULL) { 988 logmsg(LOG_ERR, "Unable to allocate kernel dump key."); 989 nerr++; 990 goto closeall; 991 } 992 993 if (read(fddev, dumpkey, dumpkeysize) != (ssize_t)dumpkeysize) { 994 logmsg(LOG_ERR, "Unable to read kernel dump key: %m."); 995 nerr++; 996 goto closeall; 997 } 998 999 snprintf(keyname, sizeof(keyname), "key.%d", bounds); 1000 ret = writekey(savedirfd, keyname, dumpkey, dumpkeysize); 1001 explicit_bzero(dumpkey, dumpkeysize); 1002 if (!ret) { 1003 nerr++; 1004 goto closeall; 1005 } 1006 } 1007 1008 logmsg(LOG_NOTICE, "writing %s%score to %s/%s", 1009 isencrypted ? "encrypted " : "", compress ? "compressed " : "", 1010 savedir, corename); 1011 1012 if (istextdump) { 1013 if (DoTextdumpFile(fddev, dumplength, lasthd, buf, device, 1014 corename, core) < 0) 1015 goto closeall; 1016 } else { 1017 if (DoRegularFile(fddev, dumplength, sectorsize, 1018 !(compress || iscompressed || isencrypted), 1019 uncompress ? kdhl.compression : KERNELDUMP_COMP_NONE, 1020 buf, device, corename, core) < 0) { 1021 goto closeall; 1022 } 1023 } 1024 if (verbose) 1025 printf("\n"); 1026 1027 if (fclose(core) < 0) { 1028 logmsg(LOG_ERR, "error on %s: %m", corename); 1029 nerr++; 1030 goto closefd; 1031 } 1032 1033 symlinks_remove(savedirfd); 1034 if (symlinkat(infoname, savedirfd, "info.last") == -1) { 1035 logmsg(LOG_WARNING, "unable to create symlink %s/%s: %m", 1036 savedir, "info.last"); 1037 } 1038 if (isencrypted) { 1039 if (symlinkat(keyname, savedirfd, "key.last") == -1) { 1040 logmsg(LOG_WARNING, 1041 "unable to create symlink %s/%s: %m", savedir, 1042 "key.last"); 1043 } 1044 } 1045 if ((iscompressed && !uncompress) || compress) { 1046 snprintf(linkname, sizeof(linkname), "%s.last.%s", 1047 istextdump ? "textdump.tar" : 1048 (isencrypted ? "vmcore_encrypted" : "vmcore"), 1049 (kdhl.compression == KERNELDUMP_COMP_ZSTD) ? "zst" : "gz"); 1050 } else { 1051 snprintf(linkname, sizeof(linkname), "%s.last", 1052 istextdump ? "textdump.tar" : 1053 (isencrypted ? "vmcore_encrypted" : "vmcore")); 1054 } 1055 if (symlinkat(corename, savedirfd, linkname) == -1) { 1056 logmsg(LOG_WARNING, "unable to create symlink %s/%s: %m", 1057 savedir, linkname); 1058 } 1059 1060 nsaved++; 1061 1062 if (verbose) 1063 printf("dump saved\n"); 1064 1065 nuke: 1066 if (!keep) { 1067 if (verbose) 1068 printf("clearing dump header\n"); 1069 memcpy(kdhl.magic, KERNELDUMPMAGIC_CLEARED, sizeof(kdhl.magic)); 1070 memcpy(temp, &kdhl, sizeof(kdhl)); 1071 if (lseek(fddev, lasthd, SEEK_SET) != lasthd || 1072 write(fddev, temp, sectorsize) != (ssize_t)sectorsize) 1073 logmsg(LOG_ERR, 1074 "error while clearing the dump header: %m"); 1075 } 1076 xo_close_container_h(xostdout, "crashdump"); 1077 xo_finish_h(xostdout); 1078 free(dumpkey); 1079 free(temp); 1080 close(fddev); 1081 return; 1082 1083 closeall: 1084 fclose(core); 1085 1086 closefd: 1087 free(dumpkey); 1088 free(temp); 1089 close(fddev); 1090 } 1091 1092 /* Prepend "/dev/" to any arguments that don't already have it */ 1093 static char ** 1094 devify(int argc, char **argv) 1095 { 1096 char **devs; 1097 int i, l; 1098 1099 devs = malloc(argc * sizeof(*argv)); 1100 if (devs == NULL) { 1101 logmsg(LOG_ERR, "malloc(): %m"); 1102 exit(1); 1103 } 1104 for (i = 0; i < argc; i++) { 1105 if (strncmp(argv[i], _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0) 1106 devs[i] = strdup(argv[i]); 1107 else { 1108 char *fullpath; 1109 1110 fullpath = malloc(PATH_MAX); 1111 if (fullpath == NULL) { 1112 logmsg(LOG_ERR, "malloc(): %m"); 1113 exit(1); 1114 } 1115 l = snprintf(fullpath, PATH_MAX, "%s%s", _PATH_DEV, 1116 argv[i]); 1117 if (l < 0) { 1118 logmsg(LOG_ERR, "snprintf(): %m"); 1119 exit(1); 1120 } else if (l >= PATH_MAX) { 1121 logmsg(LOG_ERR, "device name too long"); 1122 exit(1); 1123 } 1124 devs[i] = fullpath; 1125 } 1126 } 1127 return (devs); 1128 } 1129 1130 static char ** 1131 enum_dumpdevs(int *argcp) 1132 { 1133 struct fstab *fsp; 1134 char **argv; 1135 int argc, n; 1136 1137 /* 1138 * We cannot use getfsent(3) in capability mode, so we must 1139 * scan /etc/fstab and build up a list of candidate devices 1140 * before proceeding. 1141 */ 1142 argc = 0; 1143 n = 8; 1144 argv = malloc(n * sizeof(*argv)); 1145 if (argv == NULL) { 1146 logmsg(LOG_ERR, "malloc(): %m"); 1147 exit(1); 1148 } 1149 for (;;) { 1150 fsp = getfsent(); 1151 if (fsp == NULL) 1152 break; 1153 if (strcmp(fsp->fs_vfstype, "swap") != 0 && 1154 strcmp(fsp->fs_vfstype, "dump") != 0) 1155 continue; 1156 if (argc >= n) { 1157 n *= 2; 1158 argv = realloc(argv, n * sizeof(*argv)); 1159 if (argv == NULL) { 1160 logmsg(LOG_ERR, "realloc(): %m"); 1161 exit(1); 1162 } 1163 } 1164 argv[argc] = strdup(fsp->fs_spec); 1165 if (argv[argc] == NULL) { 1166 logmsg(LOG_ERR, "strdup(): %m"); 1167 exit(1); 1168 } 1169 argc++; 1170 } 1171 *argcp = argc; 1172 return (argv); 1173 } 1174 1175 static void 1176 init_caps(int argc, char **argv) 1177 { 1178 cap_rights_t rights; 1179 cap_channel_t *capcas; 1180 1181 capcas = cap_init(); 1182 if (capcas == NULL) { 1183 logmsg(LOG_ERR, "cap_init(): %m"); 1184 exit(1); 1185 } 1186 /* 1187 * The fileargs capability does not currently provide a way to limit 1188 * ioctls. 1189 */ 1190 (void)cap_rights_init(&rights, CAP_PREAD, CAP_WRITE, CAP_IOCTL); 1191 capfa = fileargs_init(argc, argv, checkfor || keep ? O_RDONLY : O_RDWR, 1192 0, &rights, FA_OPEN); 1193 if (capfa == NULL) { 1194 logmsg(LOG_ERR, "fileargs_init(): %m"); 1195 exit(1); 1196 } 1197 caph_cache_catpages(); 1198 caph_cache_tzdata(); 1199 if (caph_enter_casper() != 0) { 1200 logmsg(LOG_ERR, "caph_enter_casper(): %m"); 1201 exit(1); 1202 } 1203 capsyslog = cap_service_open(capcas, "system.syslog"); 1204 if (capsyslog == NULL) { 1205 logmsg(LOG_ERR, "cap_service_open(system.syslog): %m"); 1206 exit(1); 1207 } 1208 cap_close(capcas); 1209 } 1210 1211 static void 1212 usage(void) 1213 { 1214 xo_error("%s\n%s\n%s\n", 1215 "usage: savecore -c [-v] [device ...]", 1216 " savecore -C [-v] [device ...]", 1217 " savecore [-fkvz] [-m maxdumps] [directory [device ...]]"); 1218 exit(1); 1219 } 1220 1221 int 1222 main(int argc, char **argv) 1223 { 1224 cap_rights_t rights; 1225 const char *savedir; 1226 char **devs; 1227 int i, ch, error, savedirfd; 1228 1229 checkfor = compress = clear = force = keep = false; 1230 verbose = 0; 1231 nfound = nsaved = nerr = 0; 1232 savedir = "."; 1233 1234 openlog("savecore", LOG_PERROR, LOG_DAEMON); 1235 signal(SIGINFO, infohandler); 1236 1237 argc = xo_parse_args(argc, argv); 1238 if (argc < 0) 1239 exit(1); 1240 1241 while ((ch = getopt(argc, argv, "Ccfkm:uvz")) != -1) 1242 switch(ch) { 1243 case 'C': 1244 checkfor = true; 1245 break; 1246 case 'c': 1247 clear = true; 1248 break; 1249 case 'f': 1250 force = true; 1251 break; 1252 case 'k': 1253 keep = true; 1254 break; 1255 case 'm': 1256 maxdumps = atoi(optarg); 1257 if (maxdumps <= 0) { 1258 logmsg(LOG_ERR, "Invalid maxdump value"); 1259 exit(1); 1260 } 1261 break; 1262 case 'u': 1263 uncompress = true; 1264 break; 1265 case 'v': 1266 verbose++; 1267 break; 1268 case 'z': 1269 compress = true; 1270 break; 1271 case '?': 1272 default: 1273 usage(); 1274 } 1275 if (checkfor && (clear || force || keep)) 1276 usage(); 1277 if (clear && (compress || keep)) 1278 usage(); 1279 if (maxdumps > 0 && (checkfor || clear)) 1280 usage(); 1281 if (compress && uncompress) 1282 usage(); 1283 argc -= optind; 1284 argv += optind; 1285 if (argc >= 1 && !checkfor && !clear) { 1286 error = chdir(argv[0]); 1287 if (error) { 1288 logmsg(LOG_ERR, "chdir(%s): %m", argv[0]); 1289 exit(1); 1290 } 1291 savedir = argv[0]; 1292 argc--; 1293 argv++; 1294 } 1295 if (argc == 0) 1296 devs = enum_dumpdevs(&argc); 1297 else 1298 devs = devify(argc, argv); 1299 1300 savedirfd = open(savedir, O_RDONLY | O_DIRECTORY); 1301 if (savedirfd < 0) { 1302 logmsg(LOG_ERR, "open(%s): %m", savedir); 1303 exit(1); 1304 } 1305 (void)cap_rights_init(&rights, CAP_CREATE, CAP_FCNTL, CAP_FSTATAT, 1306 CAP_FSTATFS, CAP_PREAD, CAP_SYMLINKAT, CAP_FTRUNCATE, CAP_UNLINKAT, 1307 CAP_WRITE); 1308 if (caph_rights_limit(savedirfd, &rights) < 0) { 1309 logmsg(LOG_ERR, "cap_rights_limit(): %m"); 1310 exit(1); 1311 } 1312 1313 /* Enter capability mode. */ 1314 init_caps(argc, devs); 1315 1316 for (i = 0; i < argc; i++) 1317 DoFile(savedir, savedirfd, devs[i]); 1318 1319 if (nfound == 0) { 1320 if (checkfor) { 1321 if (verbose) 1322 printf("No dump exists\n"); 1323 exit(1); 1324 } 1325 if (verbose) 1326 logmsg(LOG_WARNING, "no dumps found"); 1327 } else if (nsaved == 0) { 1328 if (nerr != 0) { 1329 if (verbose) 1330 logmsg(LOG_WARNING, 1331 "unsaved dumps found but not saved"); 1332 exit(1); 1333 } else if (verbose) 1334 logmsg(LOG_WARNING, "no unsaved dumps found"); 1335 } else if (verbose) { 1336 logmsg(LOG_NOTICE, "%d cores saved in %s\n", nsaved, savedir); 1337 } 1338 1339 return (0); 1340 } 1341 1342 static void 1343 infohandler(int sig __unused) 1344 { 1345 got_siginfo = 1; 1346 } 1347