Index: head/sbin/savecore/savecore.c =================================================================== --- head/sbin/savecore/savecore.c (revision 321034) +++ head/sbin/savecore/savecore.c (revision 321035) @@ -1,976 +1,976 @@ /*- * Copyright (c) 2002 Poul-Henning Kamp * Copyright (c) 2002 Networks Associates Technology, Inc. * All rights reserved. * * This software was developed for the FreeBSD Project by Poul-Henning Kamp * and NAI Labs, the Security Research Division of Network Associates, Inc. * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the * DARPA CHATS research program. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The names of the authors may not be used to endorse or promote * products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * Copyright (c) 1986, 1992, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* The size of the buffer used for I/O. */ #define BUFFERSIZE (1024*1024) #define STATUS_BAD 0 #define STATUS_GOOD 1 #define STATUS_UNKNOWN 2 static int checkfor, compress, clear, force, keep, verbose; /* flags */ static int nfound, nsaved, nerr; /* statistics */ static int maxdumps; extern FILE *zopen(const char *, const char *); static sig_atomic_t got_siginfo; static void infohandler(int); static void printheader(xo_handle_t *xo, const struct kerneldumpheader *h, const char *device, int bounds, const int status) { uint64_t dumplen; time_t t; const char *stat_str; xo_flush_h(xo); xo_emit_h(xo, "{Lwc:Dump header from device}{:dump_device/%s}\n", device); xo_emit_h(xo, "{P: }{Lwc:Architecture}{:architecture/%s}\n", h->architecture); xo_emit_h(xo, "{P: }{Lwc:Architecture Version}{:architecture_version/%u}\n", dtoh32(h->architectureversion)); dumplen = dtoh64(h->dumplength); xo_emit_h(xo, "{P: }{Lwc:Dump Length}{:dump_length_bytes/%lld}\n", (long long)dumplen); xo_emit_h(xo, "{P: }{Lwc:Blocksize}{:blocksize/%d}\n", dtoh32(h->blocksize)); t = dtoh64(h->dumptime); xo_emit_h(xo, "{P: }{Lwc:Dumptime}{:dumptime/%s}", ctime(&t)); xo_emit_h(xo, "{P: }{Lwc:Hostname}{:hostname/%s}\n", h->hostname); xo_emit_h(xo, "{P: }{Lwc:Magic}{:magic/%s}\n", h->magic); - xo_emit_h(xo, "{P: }{Lwc:Version String}{:version_string/%s}\n", h->versionstring); + xo_emit_h(xo, "{P: }{Lwc:Version String}{:version_string/%s}", h->versionstring); xo_emit_h(xo, "{P: }{Lwc:Panic String}{:panic_string/%s}\n", h->panicstring); xo_emit_h(xo, "{P: }{Lwc:Dump Parity}{:dump_parity/%u}\n", h->parity); xo_emit_h(xo, "{P: }{Lwc:Bounds}{:bounds/%d}\n", bounds); switch(status) { case STATUS_BAD: stat_str = "bad"; break; case STATUS_GOOD: stat_str = "good"; break; default: stat_str = "unknown"; } xo_emit_h(xo, "{P: }{Lwc:Dump Status}{:dump_status/%s}\n", stat_str); xo_flush_h(xo); } static int getbounds(void) { FILE *fp; char buf[6]; int ret; ret = 0; if ((fp = fopen("bounds", "r")) == NULL) { if (verbose) printf("unable to open bounds file, using 0\n"); return (ret); } if (fgets(buf, sizeof buf, fp) == NULL) { if (feof(fp)) syslog(LOG_WARNING, "bounds file is empty, using 0"); else syslog(LOG_WARNING, "bounds file: %s", strerror(errno)); fclose(fp); return (ret); } errno = 0; ret = (int)strtol(buf, NULL, 10); if (ret == 0 && (errno == EINVAL || errno == ERANGE)) syslog(LOG_WARNING, "invalid value found in bounds, using 0"); fclose(fp); return (ret); } static void writebounds(int bounds) { FILE *fp; if ((fp = fopen("bounds", "w")) == NULL) { syslog(LOG_WARNING, "unable to write to bounds file: %m"); return; } if (verbose) printf("bounds number: %d\n", bounds); fprintf(fp, "%d\n", bounds); fclose(fp); } static bool writekey(const char *keyname, uint8_t *dumpkey, uint32_t dumpkeysize) { int fd; fd = open(keyname, O_WRONLY | O_CREAT | O_TRUNC, 0600); if (fd == -1) { syslog(LOG_ERR, "Unable to open %s to write the key: %m.", keyname); return (false); } if (write(fd, dumpkey, dumpkeysize) != (ssize_t)dumpkeysize) { syslog(LOG_ERR, "Unable to write the key to %s: %m.", keyname); close(fd); return (false); } close(fd); return (true); } static off_t file_size(const char *path) { struct stat sb; /* Ignore all errors, those file may not exists. */ if (stat(path, &sb) == -1) return (0); return (sb.st_size); } static off_t saved_dump_size(int bounds) { static char path[PATH_MAX]; off_t dumpsize; dumpsize = 0; (void)snprintf(path, sizeof(path), "info.%d", bounds); dumpsize += file_size(path); (void)snprintf(path, sizeof(path), "vmcore.%d", bounds); dumpsize += file_size(path); (void)snprintf(path, sizeof(path), "vmcore.%d.gz", bounds); dumpsize += file_size(path); (void)snprintf(path, sizeof(path), "textdump.tar.%d", bounds); dumpsize += file_size(path); (void)snprintf(path, sizeof(path), "textdump.tar.%d.gz", bounds); dumpsize += file_size(path); return (dumpsize); } static void saved_dump_remove(int bounds) { static char path[PATH_MAX]; (void)snprintf(path, sizeof(path), "info.%d", bounds); (void)unlink(path); (void)snprintf(path, sizeof(path), "vmcore.%d", bounds); (void)unlink(path); (void)snprintf(path, sizeof(path), "vmcore.%d.gz", bounds); (void)unlink(path); (void)snprintf(path, sizeof(path), "textdump.tar.%d", bounds); (void)unlink(path); (void)snprintf(path, sizeof(path), "textdump.tar.%d.gz", bounds); (void)unlink(path); } static void symlinks_remove(void) { (void)unlink("info.last"); (void)unlink("key.last"); (void)unlink("vmcore.last"); (void)unlink("vmcore.last.gz"); (void)unlink("vmcore_encrypted.last"); (void)unlink("vmcore_encrypted.last.gz"); (void)unlink("textdump.tar.last"); (void)unlink("textdump.tar.last.gz"); } /* * Check that sufficient space is available on the disk that holds the * save directory. */ static int check_space(const char *savedir, off_t dumpsize, int bounds) { FILE *fp; off_t available, minfree, spacefree, totfree, needed; struct statfs fsbuf; char buf[100]; if (statfs(".", &fsbuf) < 0) { syslog(LOG_ERR, "%s: %m", savedir); exit(1); } spacefree = ((off_t) fsbuf.f_bavail * fsbuf.f_bsize) / 1024; totfree = ((off_t) fsbuf.f_bfree * fsbuf.f_bsize) / 1024; if ((fp = fopen("minfree", "r")) == NULL) minfree = 0; else { if (fgets(buf, sizeof(buf), fp) == NULL) minfree = 0; else { char *endp; errno = 0; minfree = strtoll(buf, &endp, 10); if (minfree == 0 && errno != 0) minfree = -1; else { while (*endp != '\0' && isspace(*endp)) endp++; if (*endp != '\0' || minfree < 0) minfree = -1; } if (minfree < 0) syslog(LOG_WARNING, "`minfree` didn't contain a valid size " "(`%s`). Defaulting to 0", buf); } (void)fclose(fp); } available = minfree > 0 ? spacefree - minfree : totfree; needed = dumpsize / 1024 + 2; /* 2 for info file */ needed -= saved_dump_size(bounds); if (available < needed) { syslog(LOG_WARNING, "no dump: not enough free space on device (need at least " "%jdkB for dump; %jdkB available; %jdkB reserved)", (intmax_t)needed, (intmax_t)available + minfree, (intmax_t)minfree); return (0); } if (spacefree - needed < 0) syslog(LOG_WARNING, "dump performed, but free space threshold crossed"); return (1); } static bool compare_magic(const struct kerneldumpheader *kdh, const char *magic) { return (strncmp(kdh->magic, magic, sizeof(kdh->magic)) == 0); } #define BLOCKSIZE (1<<12) #define BLOCKMASK (~(BLOCKSIZE-1)) static int DoRegularFile(int fd, bool isencrypted, off_t dumpsize, char *buf, const char *device, const char *filename, FILE *fp) { int he, hs, nr, nw, wl; off_t dmpcnt, origsize; dmpcnt = 0; origsize = dumpsize; he = 0; while (dumpsize > 0) { wl = BUFFERSIZE; if (wl > dumpsize) wl = dumpsize; nr = read(fd, buf, wl); if (nr != wl) { if (nr == 0) syslog(LOG_WARNING, "WARNING: EOF on dump device"); else syslog(LOG_ERR, "read error on %s: %m", device); nerr++; return (-1); } if (compress || isencrypted) { nw = fwrite(buf, 1, wl, fp); } else { for (nw = 0; nw < nr; nw = he) { /* find a contiguous block of zeroes */ for (hs = nw; hs < nr; hs += BLOCKSIZE) { for (he = hs; he < nr && buf[he] == 0; ++he) /* nothing */ ; /* is the hole long enough to matter? */ if (he >= hs + BLOCKSIZE) break; } /* back down to a block boundary */ he &= BLOCKMASK; /* * 1) Don't go beyond the end of the buffer. * 2) If the end of the buffer is less than * BLOCKSIZE bytes away, we're at the end * of the file, so just grab what's left. */ if (hs + BLOCKSIZE > nr) hs = he = nr; /* * At this point, we have a partial ordering: * nw <= hs <= he <= nr * If hs > nw, buf[nw..hs] contains non-zero data. * If he > hs, buf[hs..he] is all zeroes. */ if (hs > nw) if (fwrite(buf + nw, hs - nw, 1, fp) != 1) break; if (he > hs) if (fseeko(fp, he - hs, SEEK_CUR) == -1) break; } } if (nw != wl) { syslog(LOG_ERR, "write error on %s file: %m", filename); syslog(LOG_WARNING, "WARNING: vmcore may be incomplete"); nerr++; return (-1); } if (verbose) { dmpcnt += wl; printf("%llu\r", (unsigned long long)dmpcnt); fflush(stdout); } dumpsize -= wl; if (got_siginfo) { printf("%s %.1lf%%\n", filename, (100.0 - (100.0 * (double)dumpsize / (double)origsize))); got_siginfo = 0; } } return (0); } /* * Specialized version of dump-reading logic for use with textdumps, which * are written backwards from the end of the partition, and must be reversed * before being written to the file. Textdumps are small, so do a bit less * work to optimize/sparsify. */ static int DoTextdumpFile(int fd, off_t dumpsize, off_t lasthd, char *buf, const char *device, const char *filename, FILE *fp) { int nr, nw, wl; off_t dmpcnt, totsize; totsize = dumpsize; dmpcnt = 0; wl = 512; if ((dumpsize % wl) != 0) { syslog(LOG_ERR, "textdump uneven multiple of 512 on %s", device); nerr++; return (-1); } while (dumpsize > 0) { nr = pread(fd, buf, wl, lasthd - (totsize - dumpsize) - wl); if (nr != wl) { if (nr == 0) syslog(LOG_WARNING, "WARNING: EOF on dump device"); else syslog(LOG_ERR, "read error on %s: %m", device); nerr++; return (-1); } nw = fwrite(buf, 1, wl, fp); if (nw != wl) { syslog(LOG_ERR, "write error on %s file: %m", filename); syslog(LOG_WARNING, "WARNING: textdump may be incomplete"); nerr++; return (-1); } if (verbose) { dmpcnt += wl; printf("%llu\r", (unsigned long long)dmpcnt); fflush(stdout); } dumpsize -= wl; } return (0); } static void DoFile(const char *savedir, const char *device) { xo_handle_t *xostdout, *xoinfo; static char infoname[PATH_MAX], corename[PATH_MAX], linkname[PATH_MAX]; static char keyname[PATH_MAX]; static char *buf = NULL; char *temp = NULL; struct kerneldumpheader kdhf, kdhl; uint8_t *dumpkey; off_t mediasize, dumpsize, firsthd, lasthd; FILE *info, *fp; mode_t oumask; int fd, fdinfo, error; int bounds, status; u_int sectorsize, xostyle; int istextdump; uint32_t dumpkeysize; bool isencrypted, ret; bounds = getbounds(); dumpkey = NULL; mediasize = 0; status = STATUS_UNKNOWN; xostdout = xo_create_to_file(stdout, XO_STYLE_TEXT, 0); if (xostdout == NULL) { syslog(LOG_ERR, "%s: %m", infoname); return; } if (maxdumps > 0 && bounds == maxdumps) bounds = 0; if (buf == NULL) { buf = malloc(BUFFERSIZE); if (buf == NULL) { syslog(LOG_ERR, "%m"); return; } } if (verbose) printf("checking for kernel dump on device %s\n", device); fd = open(device, (checkfor || keep) ? O_RDONLY : O_RDWR); if (fd < 0) { syslog(LOG_ERR, "%s: %m", device); return; } error = ioctl(fd, DIOCGMEDIASIZE, &mediasize); if (!error) error = ioctl(fd, DIOCGSECTORSIZE, §orsize); if (error) { syslog(LOG_ERR, "couldn't find media and/or sector size of %s: %m", device); goto closefd; } if (verbose) { printf("mediasize = %lld bytes\n", (long long)mediasize); printf("sectorsize = %u bytes\n", sectorsize); } if (sectorsize < sizeof(kdhl)) { syslog(LOG_ERR, "Sector size is less the kernel dump header %zu", sizeof(kdhl)); goto closefd; } lasthd = mediasize - sectorsize; temp = malloc(sectorsize); if (temp == NULL) { syslog(LOG_ERR, "%m"); goto closefd; } if (lseek(fd, lasthd, SEEK_SET) != lasthd || read(fd, temp, sectorsize) != (ssize_t)sectorsize) { syslog(LOG_ERR, "error reading last dump header at offset %lld in %s: %m", (long long)lasthd, device); goto closefd; } memcpy(&kdhl, temp, sizeof(kdhl)); istextdump = 0; if (compare_magic(&kdhl, TEXTDUMPMAGIC)) { if (verbose) printf("textdump magic on last dump header on %s\n", device); istextdump = 1; if (dtoh32(kdhl.version) != KERNELDUMP_TEXT_VERSION) { syslog(LOG_ERR, "unknown version (%d) in last dump header on %s", dtoh32(kdhl.version), device); status = STATUS_BAD; if (force == 0) goto closefd; } } else if (compare_magic(&kdhl, KERNELDUMPMAGIC)) { if (dtoh32(kdhl.version) != KERNELDUMPVERSION) { syslog(LOG_ERR, "unknown version (%d) in last dump header on %s", dtoh32(kdhl.version), device); status = STATUS_BAD; if (force == 0) goto closefd; } } else { if (verbose) printf("magic mismatch on last dump header on %s\n", device); status = STATUS_BAD; if (force == 0) goto closefd; if (compare_magic(&kdhl, KERNELDUMPMAGIC_CLEARED)) { if (verbose) printf("forcing magic on %s\n", device); memcpy(kdhl.magic, KERNELDUMPMAGIC, sizeof kdhl.magic); } else { syslog(LOG_ERR, "unable to force dump - bad magic"); goto closefd; } if (dtoh32(kdhl.version) != KERNELDUMPVERSION) { syslog(LOG_ERR, "unknown version (%d) in last dump header on %s", dtoh32(kdhl.version), device); status = STATUS_BAD; if (force == 0) goto closefd; } } nfound++; if (clear) goto nuke; if (kerneldump_parity(&kdhl)) { syslog(LOG_ERR, "parity error on last dump header on %s", device); nerr++; status = STATUS_BAD; if (force == 0) goto closefd; } dumpsize = dtoh64(kdhl.dumplength); dumpkeysize = dtoh32(kdhl.dumpkeysize); firsthd = lasthd - dumpsize - sectorsize - dumpkeysize; if (lseek(fd, firsthd, SEEK_SET) != firsthd || read(fd, temp, sectorsize) != (ssize_t)sectorsize) { syslog(LOG_ERR, "error reading first dump header at offset %lld in %s: %m", (long long)firsthd, device); nerr++; goto closefd; } memcpy(&kdhf, temp, sizeof(kdhf)); if (verbose >= 2) { printf("First dump headers:\n"); printheader(xostdout, &kdhf, device, bounds, -1); printf("\nLast dump headers:\n"); printheader(xostdout, &kdhl, device, bounds, -1); printf("\n"); } if (memcmp(&kdhl, &kdhf, sizeof(kdhl))) { syslog(LOG_ERR, "first and last dump headers disagree on %s", device); nerr++; status = STATUS_BAD; if (force == 0) goto closefd; } else { status = STATUS_GOOD; } if (checkfor) { printf("A dump exists on %s\n", device); close(fd); exit(0); } if (kdhl.panicstring[0] != '\0') syslog(LOG_ALERT, "reboot after panic: %.*s", (int)sizeof(kdhl.panicstring), kdhl.panicstring); else syslog(LOG_ALERT, "reboot"); if (verbose) printf("Checking for available free space\n"); if (!check_space(savedir, dumpsize, bounds)) { nerr++; goto closefd; } writebounds(bounds + 1); saved_dump_remove(bounds); snprintf(infoname, sizeof(infoname), "info.%d", bounds); /* * Create or overwrite any existing dump header files. */ fdinfo = open(infoname, O_WRONLY | O_CREAT | O_TRUNC, 0600); if (fdinfo < 0) { syslog(LOG_ERR, "%s: %m", infoname); nerr++; goto closefd; } oumask = umask(S_IRWXG|S_IRWXO); /* Restrict access to the core file.*/ isencrypted = (dumpkeysize > 0); if (compress) { snprintf(corename, sizeof(corename), "%s.%d.gz", istextdump ? "textdump.tar" : (isencrypted ? "vmcore_encrypted" : "vmcore"), bounds); fp = zopen(corename, "w"); } else { snprintf(corename, sizeof(corename), "%s.%d", istextdump ? "textdump.tar" : (isencrypted ? "vmcore_encrypted" : "vmcore"), bounds); fp = fopen(corename, "w"); } if (fp == NULL) { syslog(LOG_ERR, "%s: %m", corename); close(fdinfo); nerr++; goto closefd; } (void)umask(oumask); info = fdopen(fdinfo, "w"); if (info == NULL) { syslog(LOG_ERR, "fdopen failed: %m"); nerr++; goto closeall; } xostyle = xo_get_style(NULL); xoinfo = xo_create_to_file(info, xostyle, 0); if (xoinfo == NULL) { syslog(LOG_ERR, "%s: %m", infoname); nerr++; goto closeall; } xo_open_container_h(xoinfo, "crashdump"); if (verbose) printheader(xostdout, &kdhl, device, bounds, status); printheader(xoinfo, &kdhl, device, bounds, status); xo_close_container_h(xoinfo, "crashdump"); xo_flush_h(xoinfo); xo_finish_h(xoinfo); fclose(info); if (isencrypted) { dumpkey = calloc(1, dumpkeysize); if (dumpkey == NULL) { syslog(LOG_ERR, "Unable to allocate kernel dump key."); nerr++; goto closeall; } if (read(fd, dumpkey, dumpkeysize) != (ssize_t)dumpkeysize) { syslog(LOG_ERR, "Unable to read kernel dump key: %m."); nerr++; goto closeall; } snprintf(keyname, sizeof(keyname), "key.%d", bounds); ret = writekey(keyname, dumpkey, dumpkeysize); explicit_bzero(dumpkey, dumpkeysize); if (!ret) { nerr++; goto closeall; } } syslog(LOG_NOTICE, "writing %s%score to %s/%s", isencrypted ? "encrypted " : "", compress ? "compressed " : "", savedir, corename); if (istextdump) { if (DoTextdumpFile(fd, dumpsize, lasthd, buf, device, corename, fp) < 0) goto closeall; } else { if (DoRegularFile(fd, isencrypted, dumpsize, buf, device, corename, fp) < 0) { goto closeall; } } if (verbose) printf("\n"); if (fclose(fp) < 0) { syslog(LOG_ERR, "error on %s: %m", corename); nerr++; goto closefd; } symlinks_remove(); if (symlink(infoname, "info.last") == -1) { syslog(LOG_WARNING, "unable to create symlink %s/%s: %m", savedir, "info.last"); } if (isencrypted) { if (symlink(keyname, "key.last") == -1) { syslog(LOG_WARNING, "unable to create symlink %s/%s: %m", savedir, "key.last"); } } if (compress) { snprintf(linkname, sizeof(linkname), "%s.last.gz", istextdump ? "textdump.tar" : (isencrypted ? "vmcore_encrypted" : "vmcore")); } else { snprintf(linkname, sizeof(linkname), "%s.last", istextdump ? "textdump.tar" : (isencrypted ? "vmcore_encrypted" : "vmcore")); } if (symlink(corename, linkname) == -1) { syslog(LOG_WARNING, "unable to create symlink %s/%s: %m", savedir, linkname); } nsaved++; if (verbose) printf("dump saved\n"); nuke: if (!keep) { if (verbose) printf("clearing dump header\n"); memcpy(kdhl.magic, KERNELDUMPMAGIC_CLEARED, sizeof(kdhl.magic)); memcpy(temp, &kdhl, sizeof(kdhl)); if (lseek(fd, lasthd, SEEK_SET) != lasthd || write(fd, temp, sectorsize) != (ssize_t)sectorsize) syslog(LOG_ERR, "error while clearing the dump header: %m"); } xo_close_container_h(xostdout, "crashdump"); xo_finish_h(xostdout); free(dumpkey); free(temp); close(fd); return; closeall: fclose(fp); closefd: free(dumpkey); free(temp); close(fd); } static void usage(void) { xo_error("%s\n%s\n%s\n", "usage: savecore -c [-v] [device ...]", " savecore -C [-v] [device ...]", " savecore [-fkvz] [-m maxdumps] [directory [device ...]]"); exit(1); } int main(int argc, char **argv) { const char *savedir = "."; struct fstab *fsp; int i, ch, error; checkfor = compress = clear = force = keep = verbose = 0; nfound = nsaved = nerr = 0; openlog("savecore", LOG_PERROR, LOG_DAEMON); signal(SIGINFO, infohandler); argc = xo_parse_args(argc, argv); if (argc < 0) exit(1); while ((ch = getopt(argc, argv, "Ccfkm:vz")) != -1) switch(ch) { case 'C': checkfor = 1; break; case 'c': clear = 1; break; case 'f': force = 1; break; case 'k': keep = 1; break; case 'm': maxdumps = atoi(optarg); if (maxdumps <= 0) { syslog(LOG_ERR, "Invalid maxdump value"); exit(1); } break; case 'v': verbose++; break; case 'z': compress = 1; break; case '?': default: usage(); } if (checkfor && (clear || force || keep)) usage(); if (clear && (compress || keep)) usage(); if (maxdumps > 0 && (checkfor || clear)) usage(); argc -= optind; argv += optind; if (argc >= 1 && !checkfor && !clear) { error = chdir(argv[0]); if (error) { syslog(LOG_ERR, "chdir(%s): %m", argv[0]); exit(1); } savedir = argv[0]; argc--; argv++; } if (argc == 0) { for (;;) { fsp = getfsent(); if (fsp == NULL) break; if (strcmp(fsp->fs_vfstype, "swap") && strcmp(fsp->fs_vfstype, "dump")) continue; DoFile(savedir, fsp->fs_spec); } endfsent(); } else { for (i = 0; i < argc; i++) DoFile(savedir, argv[i]); } /* Emit minimal output. */ if (nfound == 0) { if (checkfor) { if (verbose) printf("No dump exists\n"); exit(1); } if (verbose) syslog(LOG_WARNING, "no dumps found"); } else if (nsaved == 0) { if (nerr != 0) { if (verbose) syslog(LOG_WARNING, "unsaved dumps found but not saved"); exit(1); } else if (verbose) syslog(LOG_WARNING, "no unsaved dumps found"); } return (0); } static void infohandler(int sig __unused) { got_siginfo = 1; } Index: head/sys/kern/kern_shutdown.c =================================================================== --- head/sys/kern/kern_shutdown.c (revision 321034) +++ head/sys/kern/kern_shutdown.c (revision 321035) @@ -1,1258 +1,1261 @@ /*- * Copyright (c) 1986, 1988, 1991, 1993 * The Regents of the University of California. All rights reserved. * (c) UNIX System Laboratories, Inc. * All or some portions of this file are derived from material licensed * to the University of California by American Telephone and Telegraph * Co. or Unix System Laboratories, Inc. and are reproduced herein with * the permission of UNIX System Laboratories, Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)kern_shutdown.c 8.3 (Berkeley) 1/21/94 */ #include __FBSDID("$FreeBSD$"); #include "opt_ddb.h" #include "opt_ekcd.h" #include "opt_kdb.h" #include "opt_panic.h" #include "opt_sched.h" #include "opt_watchdog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static MALLOC_DEFINE(M_DUMPER, "dumper", "dumper block buffer"); #ifndef PANIC_REBOOT_WAIT_TIME #define PANIC_REBOOT_WAIT_TIME 15 /* default to 15 seconds */ #endif static int panic_reboot_wait_time = PANIC_REBOOT_WAIT_TIME; SYSCTL_INT(_kern, OID_AUTO, panic_reboot_wait_time, CTLFLAG_RWTUN, &panic_reboot_wait_time, 0, "Seconds to wait before rebooting after a panic"); /* * Note that stdarg.h and the ANSI style va_start macro is used for both * ANSI and traditional C compilers. */ #include #ifdef KDB #ifdef KDB_UNATTENDED int debugger_on_panic = 0; #else int debugger_on_panic = 1; #endif SYSCTL_INT(_debug, OID_AUTO, debugger_on_panic, CTLFLAG_RWTUN | CTLFLAG_SECURE, &debugger_on_panic, 0, "Run debugger on kernel panic"); #ifdef KDB_TRACE static int trace_on_panic = 1; #else static int trace_on_panic = 0; #endif SYSCTL_INT(_debug, OID_AUTO, trace_on_panic, CTLFLAG_RWTUN | CTLFLAG_SECURE, &trace_on_panic, 0, "Print stack trace on kernel panic"); #endif /* KDB */ static int sync_on_panic = 0; SYSCTL_INT(_kern, OID_AUTO, sync_on_panic, CTLFLAG_RWTUN, &sync_on_panic, 0, "Do a sync before rebooting from a panic"); static SYSCTL_NODE(_kern, OID_AUTO, shutdown, CTLFLAG_RW, 0, "Shutdown environment"); #ifndef DIAGNOSTIC static int show_busybufs; #else static int show_busybufs = 1; #endif SYSCTL_INT(_kern_shutdown, OID_AUTO, show_busybufs, CTLFLAG_RW, &show_busybufs, 0, ""); int suspend_blocked = 0; SYSCTL_INT(_kern, OID_AUTO, suspend_blocked, CTLFLAG_RW, &suspend_blocked, 0, "Block suspend due to a pending shutdown"); #ifdef EKCD FEATURE(ekcd, "Encrypted kernel crash dumps support"); MALLOC_DEFINE(M_EKCD, "ekcd", "Encrypted kernel crash dumps data"); struct kerneldumpcrypto { uint8_t kdc_encryption; uint8_t kdc_iv[KERNELDUMP_IV_MAX_SIZE]; keyInstance kdc_ki; cipherInstance kdc_ci; off_t kdc_nextoffset; uint32_t kdc_dumpkeysize; struct kerneldumpkey kdc_dumpkey[]; }; #endif /* * Variable panicstr contains argument to first call to panic; used as flag * to indicate that the kernel has already called panic. */ const char *panicstr; int dumping; /* system is dumping */ int rebooting; /* system is rebooting */ static struct dumperinfo dumper; /* our selected dumper */ /* Context information for dump-debuggers. */ static struct pcb dumppcb; /* Registers. */ lwpid_t dumptid; /* Thread ID. */ static struct cdevsw reroot_cdevsw = { .d_version = D_VERSION, .d_name = "reroot", }; static void poweroff_wait(void *, int); static void shutdown_halt(void *junk, int howto); static void shutdown_panic(void *junk, int howto); static void shutdown_reset(void *junk, int howto); static int kern_reroot(void); /* register various local shutdown events */ static void shutdown_conf(void *unused) { EVENTHANDLER_REGISTER(shutdown_final, poweroff_wait, NULL, SHUTDOWN_PRI_FIRST); EVENTHANDLER_REGISTER(shutdown_final, shutdown_halt, NULL, SHUTDOWN_PRI_LAST + 100); EVENTHANDLER_REGISTER(shutdown_final, shutdown_panic, NULL, SHUTDOWN_PRI_LAST + 100); EVENTHANDLER_REGISTER(shutdown_final, shutdown_reset, NULL, SHUTDOWN_PRI_LAST + 200); } SYSINIT(shutdown_conf, SI_SUB_INTRINSIC, SI_ORDER_ANY, shutdown_conf, NULL); /* * The only reason this exists is to create the /dev/reroot/ directory, * used by reroot code in init(8) as a mountpoint for tmpfs. */ static void reroot_conf(void *unused) { int error; struct cdev *cdev; error = make_dev_p(MAKEDEV_CHECKNAME | MAKEDEV_WAITOK, &cdev, &reroot_cdevsw, NULL, UID_ROOT, GID_WHEEL, 0600, "reroot/reroot"); if (error != 0) { printf("%s: failed to create device node, error %d", __func__, error); } } SYSINIT(reroot_conf, SI_SUB_DEVFS, SI_ORDER_ANY, reroot_conf, NULL); /* * The system call that results in a reboot. */ /* ARGSUSED */ int sys_reboot(struct thread *td, struct reboot_args *uap) { int error; error = 0; #ifdef MAC error = mac_system_check_reboot(td->td_ucred, uap->opt); #endif if (error == 0) error = priv_check(td, PRIV_REBOOT); if (error == 0) { if (uap->opt & RB_REROOT) { error = kern_reroot(); } else { mtx_lock(&Giant); kern_reboot(uap->opt); mtx_unlock(&Giant); } } return (error); } /* * Called by events that want to shut down.. e.g on a PC */ void shutdown_nice(int howto) { if (initproc != NULL) { /* Send a signal to init(8) and have it shutdown the world. */ PROC_LOCK(initproc); if (howto & RB_POWEROFF) kern_psignal(initproc, SIGUSR2); else if (howto & RB_HALT) kern_psignal(initproc, SIGUSR1); else kern_psignal(initproc, SIGINT); PROC_UNLOCK(initproc); } else { /* No init(8) running, so simply reboot. */ kern_reboot(howto | RB_NOSYNC); } } static void print_uptime(void) { int f; struct timespec ts; getnanouptime(&ts); printf("Uptime: "); f = 0; if (ts.tv_sec >= 86400) { printf("%ldd", (long)ts.tv_sec / 86400); ts.tv_sec %= 86400; f = 1; } if (f || ts.tv_sec >= 3600) { printf("%ldh", (long)ts.tv_sec / 3600); ts.tv_sec %= 3600; f = 1; } if (f || ts.tv_sec >= 60) { printf("%ldm", (long)ts.tv_sec / 60); ts.tv_sec %= 60; f = 1; } printf("%lds\n", (long)ts.tv_sec); } int doadump(boolean_t textdump) { boolean_t coredump; int error; error = 0; if (dumping) return (EBUSY); if (dumper.dumper == NULL) return (ENXIO); savectx(&dumppcb); dumptid = curthread->td_tid; dumping++; coredump = TRUE; #ifdef DDB if (textdump && textdump_pending) { coredump = FALSE; textdump_dumpsys(&dumper); } #endif if (coredump) error = dumpsys(&dumper); dumping--; return (error); } /* * Shutdown the system cleanly to prepare for reboot, halt, or power off. */ void kern_reboot(int howto) { static int once = 0; #if defined(SMP) /* * Bind us to CPU 0 so that all shutdown code runs there. Some * systems don't shutdown properly (i.e., ACPI power off) if we * run on another processor. */ if (!SCHEDULER_STOPPED()) { thread_lock(curthread); sched_bind(curthread, 0); thread_unlock(curthread); KASSERT(PCPU_GET(cpuid) == 0, ("boot: not running on cpu 0")); } #endif /* We're in the process of rebooting. */ rebooting = 1; /* We are out of the debugger now. */ kdb_active = 0; /* * Do any callouts that should be done BEFORE syncing the filesystems. */ EVENTHANDLER_INVOKE(shutdown_pre_sync, howto); /* * Now sync filesystems */ if (!cold && (howto & RB_NOSYNC) == 0 && once == 0) { once = 1; bufshutdown(show_busybufs); } print_uptime(); cngrab(); /* * Ok, now do things that assume all filesystem activity has * been completed. */ EVENTHANDLER_INVOKE(shutdown_post_sync, howto); if ((howto & (RB_HALT|RB_DUMP)) == RB_DUMP && !cold && !dumping) doadump(TRUE); /* Now that we're going to really halt the system... */ EVENTHANDLER_INVOKE(shutdown_final, howto); for(;;) ; /* safety against shutdown_reset not working */ /* NOTREACHED */ } /* * The system call that results in changing the rootfs. */ static int kern_reroot(void) { struct vnode *oldrootvnode, *vp; struct mount *mp, *devmp; int error; if (curproc != initproc) return (EPERM); /* * Mark the filesystem containing currently-running executable * (the temporary copy of init(8)) busy. */ vp = curproc->p_textvp; error = vn_lock(vp, LK_SHARED); if (error != 0) return (error); mp = vp->v_mount; error = vfs_busy(mp, MBF_NOWAIT); if (error != 0) { vfs_ref(mp); VOP_UNLOCK(vp, 0); error = vfs_busy(mp, 0); vn_lock(vp, LK_SHARED | LK_RETRY); vfs_rel(mp); if (error != 0) { VOP_UNLOCK(vp, 0); return (ENOENT); } if (vp->v_iflag & VI_DOOMED) { VOP_UNLOCK(vp, 0); vfs_unbusy(mp); return (ENOENT); } } VOP_UNLOCK(vp, 0); /* * Remove the filesystem containing currently-running executable * from the mount list, to prevent it from being unmounted * by vfs_unmountall(), and to avoid confusing vfs_mountroot(). * * Also preserve /dev - forcibly unmounting it could cause driver * reinitialization. */ vfs_ref(rootdevmp); devmp = rootdevmp; rootdevmp = NULL; mtx_lock(&mountlist_mtx); TAILQ_REMOVE(&mountlist, mp, mnt_list); TAILQ_REMOVE(&mountlist, devmp, mnt_list); mtx_unlock(&mountlist_mtx); oldrootvnode = rootvnode; /* * Unmount everything except for the two filesystems preserved above. */ vfs_unmountall(); /* * Add /dev back; vfs_mountroot() will move it into its new place. */ mtx_lock(&mountlist_mtx); TAILQ_INSERT_HEAD(&mountlist, devmp, mnt_list); mtx_unlock(&mountlist_mtx); rootdevmp = devmp; vfs_rel(rootdevmp); /* * Mount the new rootfs. */ vfs_mountroot(); /* * Update all references to the old rootvnode. */ mountcheckdirs(oldrootvnode, rootvnode); /* * Add the temporary filesystem back and unbusy it. */ mtx_lock(&mountlist_mtx); TAILQ_INSERT_TAIL(&mountlist, mp, mnt_list); mtx_unlock(&mountlist_mtx); vfs_unbusy(mp); return (0); } /* * If the shutdown was a clean halt, behave accordingly. */ static void shutdown_halt(void *junk, int howto) { if (howto & RB_HALT) { printf("\n"); printf("The operating system has halted.\n"); printf("Please press any key to reboot.\n\n"); switch (cngetc()) { case -1: /* No console, just die */ cpu_halt(); /* NOTREACHED */ default: howto &= ~RB_HALT; break; } } } /* * Check to see if the system paniced, pause and then reboot * according to the specified delay. */ static void shutdown_panic(void *junk, int howto) { int loop; if (howto & RB_DUMP) { if (panic_reboot_wait_time != 0) { if (panic_reboot_wait_time != -1) { printf("Automatic reboot in %d seconds - " "press a key on the console to abort\n", panic_reboot_wait_time); for (loop = panic_reboot_wait_time * 10; loop > 0; --loop) { DELAY(1000 * 100); /* 1/10th second */ /* Did user type a key? */ if (cncheckc() != -1) break; } if (!loop) return; } } else { /* zero time specified - reboot NOW */ return; } printf("--> Press a key on the console to reboot,\n"); printf("--> or switch off the system now.\n"); cngetc(); } } /* * Everything done, now reset */ static void shutdown_reset(void *junk, int howto) { printf("Rebooting...\n"); DELAY(1000000); /* wait 1 sec for printf's to complete and be read */ /* * Acquiring smp_ipi_mtx here has a double effect: * - it disables interrupts avoiding CPU0 preemption * by fast handlers (thus deadlocking against other CPUs) * - it avoids deadlocks against smp_rendezvous() or, more * generally, threads busy-waiting, with this spinlock held, * and waiting for responses by threads on other CPUs * (ie. smp_tlb_shootdown()). * * For the !SMP case it just needs to handle the former problem. */ #ifdef SMP mtx_lock_spin(&smp_ipi_mtx); #else spinlock_enter(); #endif /* cpu_boot(howto); */ /* doesn't do anything at the moment */ cpu_reset(); /* NOTREACHED */ /* assuming reset worked */ } #if defined(WITNESS) || defined(INVARIANT_SUPPORT) static int kassert_warn_only = 0; #ifdef KDB static int kassert_do_kdb = 0; #endif #ifdef KTR static int kassert_do_ktr = 0; #endif static int kassert_do_log = 1; static int kassert_log_pps_limit = 4; static int kassert_log_mute_at = 0; static int kassert_log_panic_at = 0; static int kassert_warnings = 0; SYSCTL_NODE(_debug, OID_AUTO, kassert, CTLFLAG_RW, NULL, "kassert options"); SYSCTL_INT(_debug_kassert, OID_AUTO, warn_only, CTLFLAG_RWTUN, &kassert_warn_only, 0, "KASSERT triggers a panic (1) or just a warning (0)"); #ifdef KDB SYSCTL_INT(_debug_kassert, OID_AUTO, do_kdb, CTLFLAG_RWTUN, &kassert_do_kdb, 0, "KASSERT will enter the debugger"); #endif #ifdef KTR SYSCTL_UINT(_debug_kassert, OID_AUTO, do_ktr, CTLFLAG_RWTUN, &kassert_do_ktr, 0, "KASSERT does a KTR, set this to the KTRMASK you want"); #endif SYSCTL_INT(_debug_kassert, OID_AUTO, do_log, CTLFLAG_RWTUN, &kassert_do_log, 0, "KASSERT triggers a panic (1) or just a warning (0)"); SYSCTL_INT(_debug_kassert, OID_AUTO, warnings, CTLFLAG_RWTUN, &kassert_warnings, 0, "number of KASSERTs that have been triggered"); SYSCTL_INT(_debug_kassert, OID_AUTO, log_panic_at, CTLFLAG_RWTUN, &kassert_log_panic_at, 0, "max number of KASSERTS before we will panic"); SYSCTL_INT(_debug_kassert, OID_AUTO, log_pps_limit, CTLFLAG_RWTUN, &kassert_log_pps_limit, 0, "limit number of log messages per second"); SYSCTL_INT(_debug_kassert, OID_AUTO, log_mute_at, CTLFLAG_RWTUN, &kassert_log_mute_at, 0, "max number of KASSERTS to log"); static int kassert_sysctl_kassert(SYSCTL_HANDLER_ARGS); SYSCTL_PROC(_debug_kassert, OID_AUTO, kassert, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_SECURE, NULL, 0, kassert_sysctl_kassert, "I", "set to trigger a test kassert"); static int kassert_sysctl_kassert(SYSCTL_HANDLER_ARGS) { int error, i; error = sysctl_wire_old_buffer(req, sizeof(int)); if (error == 0) { i = 0; error = sysctl_handle_int(oidp, &i, 0, req); } if (error != 0 || req->newptr == NULL) return (error); KASSERT(0, ("kassert_sysctl_kassert triggered kassert %d", i)); return (0); } /* * Called by KASSERT, this decides if we will panic * or if we will log via printf and/or ktr. */ void kassert_panic(const char *fmt, ...) { static char buf[256]; va_list ap; va_start(ap, fmt); (void)vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); /* * panic if we're not just warning, or if we've exceeded * kassert_log_panic_at warnings. */ if (!kassert_warn_only || (kassert_log_panic_at > 0 && kassert_warnings >= kassert_log_panic_at)) { va_start(ap, fmt); vpanic(fmt, ap); /* NORETURN */ } #ifdef KTR if (kassert_do_ktr) CTR0(ktr_mask, buf); #endif /* KTR */ /* * log if we've not yet met the mute limit. */ if (kassert_do_log && (kassert_log_mute_at == 0 || kassert_warnings < kassert_log_mute_at)) { static struct timeval lasterr; static int curerr; if (ppsratecheck(&lasterr, &curerr, kassert_log_pps_limit)) { printf("KASSERT failed: %s\n", buf); kdb_backtrace(); } } #ifdef KDB if (kassert_do_kdb) { kdb_enter(KDB_WHY_KASSERT, buf); } #endif atomic_add_int(&kassert_warnings, 1); } #endif /* * Panic is called on unresolvable fatal errors. It prints "panic: mesg", * and then reboots. If we are called twice, then we avoid trying to sync * the disks as this often leads to recursive panics. */ void panic(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vpanic(fmt, ap); } void vpanic(const char *fmt, va_list ap) { #ifdef SMP cpuset_t other_cpus; #endif struct thread *td = curthread; int bootopt, newpanic; static char buf[256]; spinlock_enter(); #ifdef SMP /* * stop_cpus_hard(other_cpus) should prevent multiple CPUs from * concurrently entering panic. Only the winner will proceed * further. */ if (panicstr == NULL && !kdb_active) { other_cpus = all_cpus; CPU_CLR(PCPU_GET(cpuid), &other_cpus); stop_cpus_hard(other_cpus); } #endif /* * Ensure that the scheduler is stopped while panicking, even if panic * has been entered from kdb. */ td->td_stopsched = 1; bootopt = RB_AUTOBOOT; newpanic = 0; if (panicstr) bootopt |= RB_NOSYNC; else { bootopt |= RB_DUMP; panicstr = fmt; newpanic = 1; } if (newpanic) { (void)vsnprintf(buf, sizeof(buf), fmt, ap); panicstr = buf; cngrab(); printf("panic: %s\n", buf); } else { printf("panic: "); vprintf(fmt, ap); printf("\n"); } #ifdef SMP printf("cpuid = %d\n", PCPU_GET(cpuid)); #endif printf("time = %jd\n", (intmax_t )time_second); #ifdef KDB if (newpanic && trace_on_panic) kdb_backtrace(); if (debugger_on_panic) kdb_enter(KDB_WHY_PANIC, "panic"); #endif /*thread_lock(td); */ td->td_flags |= TDF_INPANIC; /* thread_unlock(td); */ if (!sync_on_panic) bootopt |= RB_NOSYNC; kern_reboot(bootopt); } /* * Support for poweroff delay. * * Please note that setting this delay too short might power off your machine * before the write cache on your hard disk has been flushed, leading to * soft-updates inconsistencies. */ #ifndef POWEROFF_DELAY # define POWEROFF_DELAY 5000 #endif static int poweroff_delay = POWEROFF_DELAY; SYSCTL_INT(_kern_shutdown, OID_AUTO, poweroff_delay, CTLFLAG_RW, &poweroff_delay, 0, "Delay before poweroff to write disk caches (msec)"); static void poweroff_wait(void *junk, int howto) { if (!(howto & RB_POWEROFF) || poweroff_delay <= 0) return; DELAY(poweroff_delay * 1000); } /* * Some system processes (e.g. syncer) need to be stopped at appropriate * points in their main loops prior to a system shutdown, so that they * won't interfere with the shutdown process (e.g. by holding a disk buf * to cause sync to fail). For each of these system processes, register * shutdown_kproc() as a handler for one of shutdown events. */ static int kproc_shutdown_wait = 60; SYSCTL_INT(_kern_shutdown, OID_AUTO, kproc_shutdown_wait, CTLFLAG_RW, &kproc_shutdown_wait, 0, "Max wait time (sec) to stop for each process"); void kproc_shutdown(void *arg, int howto) { struct proc *p; int error; if (panicstr) return; p = (struct proc *)arg; printf("Waiting (max %d seconds) for system process `%s' to stop... ", kproc_shutdown_wait, p->p_comm); error = kproc_suspend(p, kproc_shutdown_wait * hz); if (error == EWOULDBLOCK) printf("timed out\n"); else printf("done\n"); } void kthread_shutdown(void *arg, int howto) { struct thread *td; int error; if (panicstr) return; td = (struct thread *)arg; printf("Waiting (max %d seconds) for system thread `%s' to stop... ", kproc_shutdown_wait, td->td_name); error = kthread_suspend(td, kproc_shutdown_wait * hz); if (error == EWOULDBLOCK) printf("timed out\n"); else printf("done\n"); } static char dumpdevname[sizeof(((struct cdev*)NULL)->si_name)]; SYSCTL_STRING(_kern_shutdown, OID_AUTO, dumpdevname, CTLFLAG_RD, dumpdevname, 0, "Device for kernel dumps"); #ifdef EKCD static struct kerneldumpcrypto * kerneldumpcrypto_create(size_t blocksize, uint8_t encryption, const uint8_t *key, uint32_t encryptedkeysize, const uint8_t *encryptedkey) { struct kerneldumpcrypto *kdc; struct kerneldumpkey *kdk; uint32_t dumpkeysize; dumpkeysize = roundup2(sizeof(*kdk) + encryptedkeysize, blocksize); kdc = malloc(sizeof(*kdc) + dumpkeysize, M_EKCD, M_WAITOK | M_ZERO); arc4rand(kdc->kdc_iv, sizeof(kdc->kdc_iv), 0); kdc->kdc_encryption = encryption; switch (kdc->kdc_encryption) { case KERNELDUMP_ENC_AES_256_CBC: if (rijndael_makeKey(&kdc->kdc_ki, DIR_ENCRYPT, 256, key) <= 0) goto failed; break; default: goto failed; } kdc->kdc_dumpkeysize = dumpkeysize; kdk = kdc->kdc_dumpkey; kdk->kdk_encryption = kdc->kdc_encryption; memcpy(kdk->kdk_iv, kdc->kdc_iv, sizeof(kdk->kdk_iv)); kdk->kdk_encryptedkeysize = htod32(encryptedkeysize); memcpy(kdk->kdk_encryptedkey, encryptedkey, encryptedkeysize); return (kdc); failed: explicit_bzero(kdc, sizeof(*kdc) + dumpkeysize); free(kdc, M_EKCD); return (NULL); } #endif /* EKCD */ int kerneldumpcrypto_init(struct kerneldumpcrypto *kdc) { #ifndef EKCD return (0); #else uint8_t hash[SHA256_DIGEST_LENGTH]; SHA256_CTX ctx; struct kerneldumpkey *kdk; int error; error = 0; if (kdc == NULL) return (0); /* * When a user enters ddb it can write a crash dump multiple times. * Each time it should be encrypted using a different IV. */ SHA256_Init(&ctx); SHA256_Update(&ctx, kdc->kdc_iv, sizeof(kdc->kdc_iv)); SHA256_Final(hash, &ctx); bcopy(hash, kdc->kdc_iv, sizeof(kdc->kdc_iv)); switch (kdc->kdc_encryption) { case KERNELDUMP_ENC_AES_256_CBC: if (rijndael_cipherInit(&kdc->kdc_ci, MODE_CBC, kdc->kdc_iv) <= 0) { error = EINVAL; goto out; } break; default: error = EINVAL; goto out; } kdc->kdc_nextoffset = 0; kdk = kdc->kdc_dumpkey; memcpy(kdk->kdk_iv, kdc->kdc_iv, sizeof(kdk->kdk_iv)); out: explicit_bzero(hash, sizeof(hash)); return (error); #endif } uint32_t kerneldumpcrypto_dumpkeysize(const struct kerneldumpcrypto *kdc) { #ifdef EKCD if (kdc == NULL) return (0); return (kdc->kdc_dumpkeysize); #else return (0); #endif } /* Registration of dumpers */ int set_dumper(struct dumperinfo *di, const char *devname, struct thread *td, uint8_t encryption, const uint8_t *key, uint32_t encryptedkeysize, const uint8_t *encryptedkey) { size_t wantcopy; int error; error = priv_check(td, PRIV_SETDUMPER); if (error != 0) return (error); if (di == NULL) { error = 0; goto cleanup; } if (dumper.dumper != NULL) return (EBUSY); dumper = *di; dumper.blockbuf = NULL; dumper.kdc = NULL; if (encryption != KERNELDUMP_ENC_NONE) { #ifdef EKCD dumper.kdc = kerneldumpcrypto_create(di->blocksize, encryption, key, encryptedkeysize, encryptedkey); if (dumper.kdc == NULL) { error = EINVAL; goto cleanup; } #else error = EOPNOTSUPP; goto cleanup; #endif } wantcopy = strlcpy(dumpdevname, devname, sizeof(dumpdevname)); if (wantcopy >= sizeof(dumpdevname)) { printf("set_dumper: device name truncated from '%s' -> '%s'\n", devname, dumpdevname); } dumper.blockbuf = malloc(di->blocksize, M_DUMPER, M_WAITOK | M_ZERO); return (0); cleanup: #ifdef EKCD if (dumper.kdc != NULL) { explicit_bzero(dumper.kdc, sizeof(*dumper.kdc) + dumper.kdc->kdc_dumpkeysize); free(dumper.kdc, M_EKCD); } #endif if (dumper.blockbuf != NULL) { explicit_bzero(dumper.blockbuf, dumper.blocksize); free(dumper.blockbuf, M_DUMPER); } explicit_bzero(&dumper, sizeof(dumper)); dumpdevname[0] = '\0'; return (error); } static int dump_check_bounds(struct dumperinfo *di, off_t offset, size_t length) { if (length != 0 && (offset < di->mediaoffset || offset - di->mediaoffset + length > di->mediasize)) { printf("Attempt to write outside dump device boundaries.\n" "offset(%jd), mediaoffset(%jd), length(%ju), mediasize(%jd).\n", (intmax_t)offset, (intmax_t)di->mediaoffset, (uintmax_t)length, (intmax_t)di->mediasize); return (ENOSPC); } return (0); } #ifdef EKCD static int dump_encrypt(struct kerneldumpcrypto *kdc, uint8_t *buf, size_t size) { switch (kdc->kdc_encryption) { case KERNELDUMP_ENC_AES_256_CBC: if (rijndael_blockEncrypt(&kdc->kdc_ci, &kdc->kdc_ki, buf, 8 * size, buf) <= 0) { return (EIO); } if (rijndael_cipherInit(&kdc->kdc_ci, MODE_CBC, buf + size - 16 /* IV size for AES-256-CBC */) <= 0) { return (EIO); } break; default: return (EINVAL); } return (0); } /* Encrypt data and call dumper. */ static int dump_encrypted_write(struct dumperinfo *di, void *virtual, vm_offset_t physical, off_t offset, size_t length) { static uint8_t buf[KERNELDUMP_BUFFER_SIZE]; struct kerneldumpcrypto *kdc; int error; size_t nbytes; off_t nextoffset; kdc = di->kdc; error = dump_check_bounds(di, offset, length); if (error != 0) return (error); /* Signal completion. */ if (virtual == NULL && physical == 0 && offset == 0 && length == 0) { return (di->dumper(di->priv, virtual, physical, offset, length)); } /* Data have to be aligned to block size. */ if ((length % di->blocksize) != 0) return (EINVAL); /* * Data have to be written continuously becase we're encrypting using * CBC mode which has this assumption. */ if (kdc->kdc_nextoffset != 0 && kdc->kdc_nextoffset != offset) return (EINVAL); nextoffset = offset + (off_t)length; while (length > 0) { nbytes = MIN(length, sizeof(buf)); bcopy(virtual, buf, nbytes); if (dump_encrypt(kdc, buf, nbytes) != 0) return (EIO); error = di->dumper(di->priv, buf, physical, offset, nbytes); if (error != 0) return (error); offset += nbytes; virtual = (void *)((uint8_t *)virtual + nbytes); length -= nbytes; } kdc->kdc_nextoffset = nextoffset; return (0); } #endif /* EKCD */ /* Call dumper with bounds checking. */ static int dump_raw_write(struct dumperinfo *di, void *virtual, vm_offset_t physical, off_t offset, size_t length) { int error; error = dump_check_bounds(di, offset, length); if (error != 0) return (error); return (di->dumper(di->priv, virtual, physical, offset, length)); } int dump_write(struct dumperinfo *di, void *virtual, vm_offset_t physical, off_t offset, size_t length) { #ifdef EKCD if (di->kdc != NULL) { return (dump_encrypted_write(di, virtual, physical, offset, length)); } #endif return (dump_raw_write(di, virtual, physical, offset, length)); } static int dump_pad(struct dumperinfo *di, void *virtual, size_t length, void **buf, size_t *size) { if (length > di->blocksize) return (ENOMEM); *size = di->blocksize; if (length == di->blocksize) { *buf = virtual; } else { *buf = di->blockbuf; memcpy(*buf, virtual, length); memset((uint8_t *)*buf + length, 0, di->blocksize - length); } return (0); } static int dump_raw_write_pad(struct dumperinfo *di, void *virtual, vm_offset_t physical, off_t offset, size_t length, size_t *size) { void *buf; int error; error = dump_pad(di, virtual, length, &buf, size); if (error != 0) return (error); return (dump_raw_write(di, buf, physical, offset, *size)); } int dump_write_pad(struct dumperinfo *di, void *virtual, vm_offset_t physical, off_t offset, size_t length, size_t *size) { void *buf; int error; error = dump_pad(di, virtual, length, &buf, size); if (error != 0) return (error); return (dump_write(di, buf, physical, offset, *size)); } int dump_write_header(struct dumperinfo *di, struct kerneldumpheader *kdh, vm_offset_t physical, off_t offset) { size_t size; int ret; ret = dump_raw_write_pad(di, kdh, physical, offset, sizeof(*kdh), &size); if (ret == 0 && size != di->blocksize) ret = EINVAL; return (ret); } int dump_write_key(struct dumperinfo *di, vm_offset_t physical, off_t offset) { #ifndef EKCD return (0); #else /* EKCD */ struct kerneldumpcrypto *kdc; kdc = di->kdc; if (kdc == NULL) return (0); return (dump_raw_write(di, kdc->kdc_dumpkey, physical, offset, kdc->kdc_dumpkeysize)); #endif /* !EKCD */ } void mkdumpheader(struct kerneldumpheader *kdh, char *magic, uint32_t archver, uint64_t dumplen, uint32_t dumpkeysize, uint32_t blksz) { + size_t dstsize; bzero(kdh, sizeof(*kdh)); strlcpy(kdh->magic, magic, sizeof(kdh->magic)); strlcpy(kdh->architecture, MACHINE_ARCH, sizeof(kdh->architecture)); kdh->version = htod32(KERNELDUMPVERSION); kdh->architectureversion = htod32(archver); kdh->dumplength = htod64(dumplen); kdh->dumptime = htod64(time_second); kdh->dumpkeysize = htod32(dumpkeysize); kdh->blocksize = htod32(blksz); strlcpy(kdh->hostname, prison0.pr_hostname, sizeof(kdh->hostname)); - strlcpy(kdh->versionstring, version, sizeof(kdh->versionstring)); + dstsize = sizeof(kdh->versionstring); + if (strlcpy(kdh->versionstring, version, dstsize) >= dstsize) + kdh->versionstring[dstsize - 2] = '\n'; if (panicstr != NULL) strlcpy(kdh->panicstring, panicstr, sizeof(kdh->panicstring)); kdh->parity = kerneldump_parity(kdh); } #ifdef DDB DB_SHOW_COMMAND(panic, db_show_panic) { if (panicstr == NULL) db_printf("panicstr not set\n"); else db_printf("panic: %s\n", panicstr); } #endif