Index: sbin/dumpon/dumpon.8 =================================================================== --- sbin/dumpon/dumpon.8 +++ sbin/dumpon/dumpon.8 @@ -28,7 +28,7 @@ .\" From: @(#)swapon.8 8.1 (Berkeley) 6/5/93 .\" $FreeBSD$ .\" -.Dd November 17, 2018 +.Dd April 20, 2019 .Dt DUMPON 8 .Os .Sh NAME @@ -36,12 +36,16 @@ .Nd "specify a device for crash dumps" .Sh SYNOPSIS .Nm +.Op Fl i Ar index +.Op Fl r .Op Fl v .Op Fl k Ar pubkey .Op Fl Z .Op Fl z .Ar device .Nm +.Op Fl i Ar index +.Op Fl r .Op Fl v .Op Fl k Ar pubkey .Op Fl Z @@ -72,8 +76,35 @@ .Va dumpon_flags . For more information on this usage, see .Xr rc.conf 5 . +.Pp +Starting in +.Fx 13.0 , +.Nm +can configure a series of fallback dump devices. +For example, an administrator may prefer netdump by default, but if the netdump +service cannot be reached or some other failure occurs, they might choose a +local disk dump as a second choice option. .Ss General options .Bl -tag -width _k_pubkey +.It Fl i Ar index +Insert the specified dump configuration into the prioritized fallback dump +device list at the specified index, starting at zero. +.Pp +If +.Fl i +is not specified, the configured dump device is appended to the prioritized +list. +.It Fl r +Remove the specified dump device configuration or configurations from the +fallback dump device list rather than inserting or appending it. +In contrast, +.Do +.Nm +off +.Dc +removes all configured devices. +Conflicts with +.Fl i . .It Fl k Ar pubkey Configure encrypted kernel dumps. .Pp @@ -96,7 +127,7 @@ .Va pubkey file should be a PEM-formatted RSA key of at least 1024 bits. .It Fl l -List the currently configured dump device, or /dev/null if no device is +List the currently configured dump device(s), or /dev/null if no devices are configured. .It Fl v Enable verbose mode. Index: sbin/dumpon/dumpon.c =================================================================== --- sbin/dumpon/dumpon.c +++ sbin/dumpon/dumpon.c @@ -308,9 +308,20 @@ if (strlen(dumpdev) == 0) (void)strlcpy(dumpdev, _PATH_DEVNULL, sizeof(dumpdev)); - if (verbose) - printf("kernel dumps on "); - printf("%s\n", dumpdev); + if (verbose) { + char *ctx, *dd; + unsigned idx; + + printf("kernel dumps on priority: device\n"); + idx = 0; + dd = strtok_r(dumpdev, ",", &ctx); + do { + printf("%u: %s\n", idx, dd); + idx++; + dd = strtok_r(NULL, ",", &ctx); + } while (dd != NULL); + } else + printf("%s\n", dumpdev); /* If netdump is enabled, print the configuration parameters. */ if (verbose) { @@ -364,14 +375,16 @@ struct addrinfo hints, *res; const char *dev, *pubkeyfile, *server, *client, *gateway; int ch, error, fd; - bool enable, gzip, list, netdump, zstd; + bool gzip, list, netdump, zstd, insert, remove; + uint8_t ins_idx; - gzip = list = netdump = zstd = false; + gzip = list = netdump = zstd = insert = remove = false; kdap = NULL; pubkeyfile = NULL; server = client = gateway = NULL; + ins_idx = KDA_APPEND; - while ((ch = getopt(argc, argv, "c:g:k:ls:vZz")) != -1) + while ((ch = getopt(argc, argv, "c:g:i:k:lrs:vZz")) != -1) switch ((char)ch) { case 'c': client = optarg; @@ -379,12 +392,28 @@ case 'g': gateway = optarg; break; + case 'i': + { + int i; + + i = atoi(optarg); + if (i < 0 || i >= KDA_APPEND - 1) + errx(EX_USAGE, + "-i index must be between zero and %d.", + (int)KDA_APPEND - 2); + insert = true; + ins_idx = i + 1; + } + break; case 'k': pubkeyfile = optarg; break; case 'l': list = true; break; + case 'r': + remove = true; + break; case 's': server = optarg; break; @@ -404,6 +433,9 @@ if (gzip && zstd) errx(EX_USAGE, "The -z and -Z options are mutually exclusive."); + if (insert && remove) + errx(EX_USAGE, "The -i and -r options are mutually exclusive."); + argc -= optind; argv += optind; @@ -422,31 +454,31 @@ #endif if (server != NULL && client != NULL) { - enable = true; dev = _PATH_NETDUMP; netdump = true; kdap = &ndconf.ndc_kda; } else if (server == NULL && client == NULL && argc > 0) { - enable = strcmp(argv[0], "off") != 0; - dev = enable ? argv[0] : _PATH_DEVNULL; + if (strcmp(argv[0], "off") == 0) { + remove = true; + dev = _PATH_DEVNULL; + } else + dev = argv[0]; netdump = false; kdap = &_kda; } else usage(); fd = opendumpdev(dev, dumpdev); - if (!netdump && !gzip) + if (!netdump && !gzip && !remove) check_size(fd, dumpdev); bzero(kdap, sizeof(*kdap)); - kdap->kda_enable = 0; - if (ioctl(fd, DIOCSKERNELDUMP, kdap) != 0) - err(EX_OSERR, "ioctl(DIOCSKERNELDUMP)"); - if (!enable) - exit(EX_OK); - explicit_bzero(kdap, sizeof(*kdap)); - kdap->kda_enable = 1; + if (remove) + kdap->kda_index = KDA_REMOVE; + else + kdap->kda_index = ins_idx; + kdap->kda_compression = KERNELDUMP_COMP_NONE; if (zstd) kdap->kda_compression = KERNELDUMP_COMP_ZSTD; @@ -517,7 +549,7 @@ errc(EX_OSERR, error, "ioctl(DIOCSKERNELDUMP)"); } if (verbose) - printf("kernel dumps on %s\n", dumpdev); + listdumpdev(); exit(EX_OK); } Index: sys/dev/null/null.c =================================================================== --- sys/dev/null/null.c +++ sys/dev/null/null.c @@ -114,7 +114,7 @@ case DIOCSKERNELDUMP_FREEBSD11: #endif case DIOCSKERNELDUMP: - error = clear_dumper(td); + error = remove_dumper(NULL, NULL); break; case FIONBIO: break; Index: sys/geom/geom_dev.c =================================================================== --- sys/geom/geom_dev.c +++ sys/geom/geom_dev.c @@ -135,15 +135,20 @@ } static int -g_dev_setdumpdev(struct cdev *dev, struct diocskerneldump_arg *kda, - struct thread *td) +g_dev_setdumpdev(struct cdev *dev, struct diocskerneldump_arg *kda) { struct g_kerneldump kd; struct g_consumer *cp; int error, len; + uint8_t idx; - if (dev == NULL || kda == NULL) - return (clear_dumper(td)); + MPASS(dev != NULL && kda != NULL); + MPASS(kda->kda_index != KDA_REMOVE); + + idx = kda->kda_index; + /* Adjust from 1-index to 0-index */ + if (idx != KDA_APPEND) + idx--; cp = dev->si_drv2; len = sizeof(kd); @@ -154,9 +159,9 @@ if (error != 0) return (error); - error = set_dumper(&kd.di, devtoname(dev), td, kda->kda_compression, - kda->kda_encryption, kda->kda_key, kda->kda_encryptedkeysize, - kda->kda_encryptedkey); + error = insert_dumper(&kd.di, devtoname(dev), idx, + kda->kda_compression, kda->kda_encryption, kda->kda_key, + kda->kda_encryptedkeysize, kda->kda_encryptedkey); if (error == 0) dev->si_flags |= SI_DUMPDEV; @@ -173,7 +178,7 @@ size_t len; bzero(&kda, sizeof(kda)); - kda.kda_enable = 1; + kda.kda_index = KDA_APPEND; if (dumpdev == NULL) return (0); @@ -190,7 +195,7 @@ if (error != 0) return (error); - error = g_dev_setdumpdev(dev, &kda, curthread); + error = g_dev_setdumpdev(dev, &kda); if (error == 0) { freeenv(dumpdev); dumpdev = NULL; @@ -549,11 +554,11 @@ bzero(&kda, sizeof(kda)); kda.kda_encryption = KERNELDUMP_ENC_NONE; - kda.kda_enable = (uint8_t)*(u_int *)data; - if (kda.kda_enable == 0) - error = g_dev_setdumpdev(NULL, NULL, td); + kda.kda_index = (uint8_t)*(u_int *)data; + if (kda.kda_index == KDA_REMOVE) + error = remove_dumper(devtoname(dev), &kda); else - error = g_dev_setdumpdev(dev, &kda, td); + error = g_dev_setdumpdev(dev, &kda); break; } #endif @@ -563,8 +568,9 @@ uint8_t *encryptedkey; kda = (struct diocskerneldump_arg *)data; - if (kda->kda_enable == 0) { - error = g_dev_setdumpdev(NULL, NULL, td); + if (kda->kda_index == KDA_REMOVE) { + error = remove_dumper(devtoname(dev), kda); + explicit_bzero(kda, sizeof(*kda)); break; } @@ -583,7 +589,7 @@ } if (error == 0) { kda->kda_encryptedkey = encryptedkey; - error = g_dev_setdumpdev(dev, kda, td); + error = g_dev_setdumpdev(dev, kda); } if (encryptedkey != NULL) { explicit_bzero(encryptedkey, kda->kda_encryptedkeysize); @@ -860,7 +866,7 @@ /* Reset any dump-area set on this device */ if (dev->si_flags & SI_DUMPDEV) - (void)clear_dumper(curthread); + (void)remove_dumper(devtoname(dev), NULL); /* Destroy the struct cdev *so we get no more requests */ destroy_dev_sched_cb(dev, g_dev_callback, cp); Index: sys/kern/kern_shutdown.c =================================================================== --- sys/kern/kern_shutdown.c +++ sys/kern/kern_shutdown.c @@ -43,6 +43,7 @@ #include "opt_ekcd.h" #include "opt_kdb.h" #include "opt_panic.h" +#include "opt_printf.h" #include "opt_sched.h" #include "opt_watchdog.h" @@ -53,6 +54,7 @@ #include #include #include +#include #include #include #include @@ -69,6 +71,7 @@ #include #include #include +#include #include #include #include @@ -209,7 +212,16 @@ int dumping; /* system is dumping */ int rebooting; /* system is rebooting */ -static struct dumperinfo dumper; /* our selected dumper */ +/* + * Only used to serialize between unprotected sysctl kern.shutdown.dumpdevname + * and list modifications. + */ +static struct mtx dumpconf_list_lk; +MTX_SYSINIT(dumper_configs, &dumpconf_list_lk, "dumper config list", MTX_DEF); + +/* our selected dumper(s) */ +static TAILQ_HEAD(dumpconflist, dumperinfo) dumper_configs = + TAILQ_HEAD_INITIALIZER(dumper_configs); /* Context information for dump-debuggers. */ static struct pcb dumppcb; /* Registers. */ @@ -364,7 +376,7 @@ error = 0; if (dumping) return (EBUSY); - if (dumper.dumper == NULL) + if (TAILQ_EMPTY(&dumper_configs)) return (ENXIO); savectx(&dumppcb); @@ -375,11 +387,18 @@ #ifdef DDB if (textdump && textdump_pending) { coredump = FALSE; - textdump_dumpsys(&dumper); + textdump_dumpsys(TAILQ_FIRST(&dumper_configs)); } #endif - if (coredump) - error = dumpsys(&dumper); + if (coredump) { + struct dumperinfo *di; + + TAILQ_FOREACH(di, &dumper_configs, di_next) { + error = dumpsys(di); + if (error == 0) + break; + } + } dumping--; return (error); @@ -952,9 +971,37 @@ 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"); +static int +dumpdevname_sysctl_handler(SYSCTL_HANDLER_ARGS) +{ + char buf[PRINTF_BUFR_SIZE]; + struct dumperinfo *di; + struct sbuf sb; + bool first; + int error; + + sbuf_new(&sb, buf, sizeof(buf), SBUF_FIXEDLEN | SBUF_INCLUDENUL); + + mtx_lock(&dumpconf_list_lk); + first = true; + TAILQ_FOREACH(di, &dumper_configs, di_next) { + if (first) + first = false; + else + sbuf_putc(&sb, ','); + sbuf_cat(&sb, di->di_devname); + } + mtx_unlock(&dumpconf_list_lk); + + error = sbuf_finish(&sb); + if (error == 0) + error = SYSCTL_OUT(req, sbuf_data(&sb), sbuf_len(&sb)); + sbuf_delete(&sb); + return (error); +} +SYSCTL_PROC(_kern_shutdown, OID_AUTO, dumpdevname, CTLTYPE_STRING | CTLFLAG_RD, + &dumper_configs, 0, dumpdevname_sysctl_handler, "A", + "Device(s) for kernel dumps"); static int _dump_append(struct dumperinfo *di, void *virtual, vm_offset_t physical, size_t length); @@ -1092,31 +1139,62 @@ free(kdcomp, M_DUMPER); } +/* + * Must not be present on global list. + */ +static void +free_single_dumper(struct dumperinfo *di) +{ + + if (di == NULL) + return; + + if (di->blockbuf != NULL) { + explicit_bzero(di->blockbuf, di->blocksize); + free(di->blockbuf, M_DUMPER); + } + + kerneldumpcomp_destroy(di); + +#ifdef EKCD + if (di->kdcrypto != NULL) { + explicit_bzero(di->kdcrypto, sizeof(*di->kdcrypto) + + di->kdcrypto->kdc_dumpkeysize); + free(di->kdcrypto, M_EKCD); + } +#endif + + explicit_bzero(di, sizeof(*di)); + free(di, M_DUMPER); +} + /* Registration of dumpers */ int -set_dumper(struct dumperinfo *di, const char *devname, struct thread *td, - uint8_t compression, uint8_t encryption, const uint8_t *key, +insert_dumper(const struct dumperinfo *di_template, const char *devname, + uint8_t index, uint8_t compression, uint8_t encryption, const uint8_t *key, uint32_t encryptedkeysize, const uint8_t *encryptedkey) { - size_t wantcopy; + struct dumperinfo *newdi, *listdi; + bool inserted; int error; - error = priv_check(td, PRIV_SETDUMPER); + error = priv_check(curthread, PRIV_SETDUMPER); if (error != 0) return (error); - if (dumper.dumper != NULL) - return (EBUSY); - dumper = *di; - dumper.blockbuf = NULL; - dumper.kdcrypto = NULL; - dumper.kdcomp = NULL; + newdi = malloc(sizeof(*newdi) + strlen(devname) + 1, M_DUMPER, M_WAITOK + | M_ZERO); + *newdi = *di_template; + newdi->blockbuf = NULL; + newdi->kdcrypto = NULL; + newdi->kdcomp = NULL; + strcpy(newdi->di_devname, devname); if (encryption != KERNELDUMP_ENC_NONE) { #ifdef EKCD - dumper.kdcrypto = kerneldumpcrypto_create(di->blocksize, + newdi->kdcrypto = kerneldumpcrypto_create(di_template->blocksize, encryption, key, encryptedkeysize, encryptedkey); - if (dumper.kdcrypto == NULL) { + if (newdi->kdcrypto == NULL) { error = EINVAL; goto cleanup; } @@ -1125,66 +1203,116 @@ 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); - } - if (compression != KERNELDUMP_COMP_NONE) { /* * We currently can't support simultaneous encryption and - * compression. + * compression because our only encryption mode is an unpadded + * block cipher, go figure. This is low hanging fruit to fix. */ if (encryption != KERNELDUMP_ENC_NONE) { error = EOPNOTSUPP; goto cleanup; } - dumper.kdcomp = kerneldumpcomp_create(&dumper, compression); - if (dumper.kdcomp == NULL) { + newdi->kdcomp = kerneldumpcomp_create(newdi, compression); + if (newdi->kdcomp == NULL) { error = EINVAL; goto cleanup; } } - dumper.blockbuf = malloc(di->blocksize, M_DUMPER, M_WAITOK | M_ZERO); + newdi->blockbuf = malloc(newdi->blocksize, M_DUMPER, M_WAITOK | M_ZERO); + + /* Add the new configuration to the queue */ + mtx_lock(&dumpconf_list_lk); + inserted = false; + TAILQ_FOREACH(listdi, &dumper_configs, di_next) { + if (index == 0) { + TAILQ_INSERT_BEFORE(listdi, newdi, di_next); + inserted = true; + break; + } + index--; + } + if (!inserted) + TAILQ_INSERT_TAIL(&dumper_configs, newdi, di_next); + mtx_unlock(&dumpconf_list_lk); + return (0); cleanup: - (void)clear_dumper(td); + free_single_dumper(newdi); return (error); } -int -clear_dumper(struct thread *td) +static bool +dumper_config_match(const struct dumperinfo *di, const char *devname, + const struct diocskerneldump_arg *kda) { - int error; + /* Special wildcard: NULL for /dev/null removes everything. */ + if (devname == NULL) + return (true); - error = priv_check(td, PRIV_SETDUMPER); - if (error != 0) - return (error); + if (strcmp(di->di_devname, devname) != 0) + return (false); -#ifdef NETDUMP - netdump_mbuf_drain(); -#endif + /* + * Allow wildcard removal of configs matching a device on g_dev_orphan. + */ + if (kda == NULL) + return (true); + if (di->kdcomp != NULL) { + if (di->kdcomp->kdc_format != kda->kda_compression) + return (false); + } else if (kda->kda_compression != KERNELDUMP_COMP_NONE) + return (false); #ifdef EKCD - if (dumper.kdcrypto != NULL) { - explicit_bzero(dumper.kdcrypto, sizeof(*dumper.kdcrypto) + - dumper.kdcrypto->kdc_dumpkeysize); - free(dumper.kdcrypto, M_EKCD); - } + if (di->kdcrypto != NULL) { + if (di->kdcrypto->kdc_encryption != kda->kda_encryption) + return (false); + /* + * Do we care to verify keys match to delete? It seems weird + * to expect multiple fallback dump configurations on the same + * device that only differ in crypto key. + */ + } else #endif + if (kda->kda_encryption != KERNELDUMP_ENC_NONE) + return (false); + + return (true); +} + +int +remove_dumper(const char *devname, const struct diocskerneldump_arg *kda) +{ + struct dumperinfo *di, *sdi; + bool found; + int error; - kerneldumpcomp_destroy(&dumper); + error = priv_check(curthread, PRIV_SETDUMPER); + if (error != 0) + return (error); - if (dumper.blockbuf != NULL) { - explicit_bzero(dumper.blockbuf, dumper.blocksize); - free(dumper.blockbuf, M_DUMPER); + /* + * Try to find a matching configuration, and kill it. + * + * NULL 'kda' indicates remove any configuration matching 'devname', + * which may remove multiple configurations in atypical configurations. + */ + found = false; + mtx_lock(&dumpconf_list_lk); + TAILQ_FOREACH_SAFE(di, &dumper_configs, di_next, sdi) { + if (dumper_config_match(di, devname, kda)) { + found = true; + TAILQ_REMOVE(&dumper_configs, di, di_next); + free_single_dumper(di); + } } - explicit_bzero(&dumper, sizeof(dumper)); - dumpdevname[0] = '\0'; + mtx_unlock(&dumpconf_list_lk); + + if (!found) + return (ENOENT); return (0); } Index: sys/netinet/netdump/netdump_client.c =================================================================== --- sys/netinet/netdump/netdump_client.c +++ sys/netinet/netdump/netdump_client.c @@ -1144,7 +1144,7 @@ struct diocskerneldump_arg *kda; struct dumperinfo dumper; struct netdump_conf *conf; - uint8_t *encryptedkey; + uint8_t *encryptedkey, idx; int error; u_int u; @@ -1165,7 +1165,7 @@ #endif case DIOCSKERNELDUMP: kda = (void *)addr; - if (kda->kda_enable != 0) { + if (kda->kda_index != KDA_REMOVE) { error = ENXIO; break; } @@ -1193,9 +1193,9 @@ kda = &conf->ndc_kda; conf->ndc_iface[sizeof(conf->ndc_iface) - 1] = '\0'; - if (kda->kda_enable == 0) { + if (kda->kda_index == KDA_REMOVE) { if (nd_enabled) { - error = clear_dumper(td); + error = remove_dumper(conf->ndc_iface, kda); if (error == 0) { nd_enabled = 0; netdump_mbuf_drain(); @@ -1204,6 +1204,11 @@ break; } + idx = kda->kda_index; + /* Adjust from 1-index to 0-index */ + if (idx != KDA_APPEND) + idx--; + error = netdump_configure(conf, td); if (error != 0) break; @@ -1233,7 +1238,7 @@ dumper.mediaoffset = 0; dumper.mediasize = 0; - error = set_dumper(&dumper, conf->ndc_iface, td, + error = insert_dumper(&dumper, conf->ndc_iface, idx, kda->kda_compression, kda->kda_encryption, kda->kda_key, kda->kda_encryptedkeysize, encryptedkey); @@ -1303,12 +1308,13 @@ } break; case MOD_UNLOAD: - destroy_dev(netdump_cdev); if (nd_enabled) { printf("netdump: disabling dump device for unload\n"); - (void)clear_dumper(curthread); + netdump_mbuf_drain(); + (void)remove_dumper(nd_conf.ndc_iface, NULL); nd_enabled = 0; } + destroy_dev(netdump_cdev); break; default: error = EOPNOTSUPP; Index: sys/sys/conf.h =================================================================== --- sys/sys/conf.h +++ sys/sys/conf.h @@ -352,15 +352,20 @@ off_t origdumpoff; /* Starting dump offset. */ struct kerneldumpcrypto *kdcrypto; /* Kernel dump crypto. */ struct kerneldumpcomp *kdcomp; /* Kernel dump compression. */ + + TAILQ_ENTRY(dumperinfo) di_next; + + char di_devname[]; }; extern int dumping; /* system is dumping */ int doadump(boolean_t); -int set_dumper(struct dumperinfo *di, const char *devname, struct thread *td, - uint8_t compression, uint8_t encryption, const uint8_t *key, +int insert_dumper(const struct dumperinfo *di_template, const char *devname, + uint8_t index, uint8_t compression, uint8_t encryption, const uint8_t *key, uint32_t encryptedkeysize, const uint8_t *encryptedkey); -int clear_dumper(struct thread *td); +struct diocskerneldump_arg; +int remove_dumper(const char *devname, const struct diocskerneldump_arg *kda); int dump_start(struct dumperinfo *di, struct kerneldumpheader *kdh); int dump_append(struct dumperinfo *, void *, vm_offset_t, size_t); Index: sys/sys/disk.h =================================================================== --- sys/sys/disk.h +++ sys/sys/disk.h @@ -143,8 +143,22 @@ #define DIOCZONECMD _IOWR('d', 143, struct disk_zone_args) +/* + * Sentinel values for kda_index. + * + * If kda_index is KDA_REMOVE, the specified dump configuration for the given + * device is removed from the list of fallback dump configurations. + * + * If kda_index is KDA_APPEND, the dump configuration is added after all + * existing dump configurations. + * + * Otherwise, the new configuration is inserted into the fallback dump list at + * index 'kda_index - 1'. + */ +#define KDA_REMOVE 0 +#define KDA_APPEND UINT8_MAX struct diocskerneldump_arg { - uint8_t kda_enable; + uint8_t kda_index; uint8_t kda_compression; uint8_t kda_encryption; uint8_t kda_key[KERNELDUMP_KEY_MAX_SIZE];