Index: head/sbin/dumpon/dumpon.8 =================================================================== --- head/sbin/dumpon/dumpon.8 +++ head/sbin/dumpon/dumpon.8 @@ -28,7 +28,7 @@ .\" From: @(#)swapon.8 8.1 (Berkeley) 6/5/93 .\" $FreeBSD$ .\" -.Dd November 17, 2018 +.Dd May 6, 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,38 @@ .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 +.Xr netdump 4 +by default, but if the +.Xr netdump 4 +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 +130,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: head/sbin/dumpon/dumpon.c =================================================================== --- head/sbin/dumpon/dumpon.c +++ head/sbin/dumpon/dumpon.c @@ -86,8 +86,8 @@ usage(void) { fprintf(stderr, - "usage: dumpon [-v] [-k ] [-Zz] \n" - " dumpon [-v] [-k ] [-Zz]\n" + "usage: dumpon [-i index] [-r] [-v] [-k ] [-Zz] \n" + " dumpon [-i index] [-r] [-v] [-k ] [-Zz]\n" " [-g ] -s -c \n" " dumpon [-v] off\n" " dumpon [-v] -l\n"); @@ -290,8 +290,10 @@ static void listdumpdev(void) { + static char ip[200]; + char dumpdev[PATH_MAX]; - struct netdump_conf ndconf; + struct diocskerneldump_arg ndconf; size_t len; const char *sysctlname = "kern.shutdown.dumpdevname"; int fd; @@ -308,10 +310,18 @@ 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; + ctx = dumpdev; + while ((dd = strsep(&ctx, ",")) != NULL) + printf("%u: %s\n", idx++, dd); + } else + printf("%s\n", dumpdev); + /* If netdump is enabled, print the configuration parameters. */ if (verbose) { fd = open(_PATH_NETDUMP, O_RDONLY); @@ -320,16 +330,22 @@ err(EX_OSERR, "opening %s", _PATH_NETDUMP); return; } - if (ioctl(fd, NETDUMPGCONF, &ndconf) != 0) { + if (ioctl(fd, DIOCGKERNELDUMP, &ndconf) != 0) { if (errno != ENXIO) - err(EX_OSERR, "ioctl(NETDUMPGCONF)"); + err(EX_OSERR, "ioctl(DIOCGKERNELDUMP)"); (void)close(fd); return; } - printf("server address: %s\n", inet_ntoa(ndconf.ndc_server)); - printf("client address: %s\n", inet_ntoa(ndconf.ndc_client)); - printf("gateway address: %s\n", inet_ntoa(ndconf.ndc_gateway)); + printf("server address: %s\n", + inet_ntop(ndconf.kda_af, &ndconf.kda_server, ip, + sizeof(ip))); + printf("client address: %s\n", + inet_ntop(ndconf.kda_af, &ndconf.kda_client, ip, + sizeof(ip))); + printf("gateway address: %s\n", + inet_ntop(ndconf.kda_af, &ndconf.kda_gateway, ip, + sizeof(ip))); (void)close(fd); } } @@ -359,19 +375,20 @@ main(int argc, char *argv[]) { char dumpdev[PATH_MAX]; - struct diocskerneldump_arg _kda, *kdap; - struct netdump_conf ndconf; + struct diocskerneldump_arg ndconf, *kdap; 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, rflag; + uint8_t ins_idx; - gzip = list = netdump = zstd = false; + gzip = list = netdump = zstd = insert = rflag = 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 +396,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; + } + break; case 'k': pubkeyfile = optarg; break; case 'l': list = true; break; + case 'r': + rflag = true; + break; case 's': server = optarg; break; @@ -404,6 +437,9 @@ if (gzip && zstd) errx(EX_USAGE, "The -z and -Z options are mutually exclusive."); + if (insert && rflag) + errx(EX_USAGE, "The -i and -r options are mutually exclusive."); + argc -= optind; argv += optind; @@ -422,31 +458,30 @@ #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) { + rflag = true; + dev = _PATH_DEVNULL; + } else + dev = argv[0]; netdump = false; - kdap = &_kda; } else usage(); fd = opendumpdev(dev, dumpdev); - if (!netdump && !gzip) + if (!netdump && !gzip && !rflag) check_size(fd, dumpdev); + kdap = &ndconf; 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 (rflag) + 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; @@ -467,12 +502,12 @@ ((struct sockaddr_in *)(void *)res->ai_addr)->sin_addr); freeaddrinfo(res); - if (strlcpy(ndconf.ndc_iface, argv[0], - sizeof(ndconf.ndc_iface)) >= sizeof(ndconf.ndc_iface)) + if (strlcpy(ndconf.kda_iface, argv[0], + sizeof(ndconf.kda_iface)) >= sizeof(ndconf.kda_iface)) errx(EX_USAGE, "invalid interface name '%s'", argv[0]); - if (inet_aton(server, &ndconf.ndc_server) == 0) + if (inet_aton(server, &ndconf.kda_server.in4) == 0) errx(EX_USAGE, "invalid server address '%s'", server); - if (inet_aton(client, &ndconf.ndc_client) == 0) + if (inet_aton(client, &ndconf.kda_client.in4) == 0) errx(EX_USAGE, "invalid client address '%s'", client); if (gateway == NULL) { @@ -485,39 +520,41 @@ gateway = server; } } - if (inet_aton(gateway, &ndconf.ndc_gateway) == 0) + if (inet_aton(gateway, &ndconf.kda_gateway.in4) == 0) errx(EX_USAGE, "invalid gateway address '%s'", gateway); + ndconf.kda_af = AF_INET; + } #ifdef HAVE_CRYPTO - if (pubkeyfile != NULL) - genkey(pubkeyfile, kdap); + if (pubkeyfile != NULL) + genkey(pubkeyfile, kdap); #endif - error = ioctl(fd, NETDUMPSCONF, &ndconf); - if (error != 0) - error = errno; - explicit_bzero(kdap->kda_encryptedkey, - kdap->kda_encryptedkeysize); - free(kdap->kda_encryptedkey); - explicit_bzero(kdap, sizeof(*kdap)); - if (error != 0) - errc(EX_OSERR, error, "ioctl(NETDUMPSCONF)"); - } else { -#ifdef HAVE_CRYPTO - if (pubkeyfile != NULL) - genkey(pubkeyfile, kdap); -#endif - error = ioctl(fd, DIOCSKERNELDUMP, kdap); - if (error != 0) - error = errno; - explicit_bzero(kdap->kda_encryptedkey, - kdap->kda_encryptedkeysize); - free(kdap->kda_encryptedkey); - explicit_bzero(kdap, sizeof(*kdap)); - if (error != 0) - errc(EX_OSERR, error, "ioctl(DIOCSKERNELDUMP)"); + error = ioctl(fd, DIOCSKERNELDUMP, kdap); + if (error != 0) + error = errno; + explicit_bzero(kdap->kda_encryptedkey, kdap->kda_encryptedkeysize); + free(kdap->kda_encryptedkey); + explicit_bzero(kdap, sizeof(*kdap)); + if (error != 0) { + if (netdump) { + /* + * Be slightly less user-hostile for some common + * errors, especially as users don't have any great + * discoverability into which NICs support netdump. + */ + if (error == ENXIO) + errx(EX_OSERR, "Unable to configure netdump " + "because the interface's link is down."); + else if (error == ENODEV) + errx(EX_OSERR, "Unable to configure netdump " + "because the interface driver does not yet " + "support netdump."); + } + errc(EX_OSERR, error, "ioctl(DIOCSKERNELDUMP)"); } + if (verbose) - printf("kernel dumps on %s\n", dumpdev); + listdumpdev(); exit(EX_OK); } Index: head/sys/dev/null/null.c =================================================================== --- head/sys/dev/null/null.c +++ head/sys/dev/null/null.c @@ -106,15 +106,26 @@ null_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t data __unused, int flags __unused, struct thread *td) { + struct diocskerneldump_arg kda; int error; error = 0; switch (cmd) { #ifdef COMPAT_FREEBSD11 case DIOCSKERNELDUMP_FREEBSD11: + gone_in(13, "FreeBSD 11.x ABI compat"); + /* FALLTHROUGH */ #endif +#ifdef COMPAT_FREEBSD12 + case DIOCSKERNELDUMP_FREEBSD12: + if (cmd == DIOCSKERNELDUMP_FREEBSD12) + gone_in(14, "FreeBSD 12.x ABI compat"); + /* FALLTHROUGH */ +#endif case DIOCSKERNELDUMP: - error = clear_dumper(td); + bzero(&kda, sizeof(kda)); + kda.kda_index = KDA_REMOVE_ALL; + error = dumper_remove(NULL, &kda); break; case FIONBIO: break; Index: head/sys/geom/geom_dev.c =================================================================== --- head/sys/geom/geom_dev.c +++ head/sys/geom/geom_dev.c @@ -135,15 +135,14 @@ } 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; - if (dev == NULL || kda == NULL) - return (clear_dumper(td)); + MPASS(dev != NULL && kda != NULL); + MPASS(kda->kda_index != KDA_REMOVE); cp = dev->si_drv2; len = sizeof(kd); @@ -154,9 +153,7 @@ 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 = dumper_insert(&kd.di, devtoname(dev), kda); if (error == 0) dev->si_flags |= SI_DUMPDEV; @@ -173,7 +170,7 @@ size_t len; bzero(&kda, sizeof(kda)); - kda.kda_enable = 1; + kda.kda_index = KDA_APPEND; if (dumpdev == NULL) return (0); @@ -190,7 +187,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; @@ -509,6 +506,9 @@ struct g_provider *pp; off_t offset, length, chunk, odd; int i, error; +#ifdef COMPAT_FREEBSD12 + struct diocskerneldump_arg kda_copy; +#endif cp = dev->si_drv2; pp = cp->provider; @@ -547,31 +547,55 @@ { struct diocskerneldump_arg kda; + gone_in(13, "FreeBSD 11.x ABI compat"); + 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 = (*(u_int *)data ? 0 : KDA_REMOVE_ALL); + if (kda.kda_index == KDA_REMOVE_ALL) + error = dumper_remove(devtoname(dev), &kda); else - error = g_dev_setdumpdev(dev, &kda, td); + error = g_dev_setdumpdev(dev, &kda); break; } #endif +#ifdef COMPAT_FREEBSD12 + case DIOCSKERNELDUMP_FREEBSD12: + { + struct diocskerneldump_arg_freebsd12 *kda12; + + gone_in(14, "FreeBSD 12.x ABI compat"); + + kda12 = (void *)data; + memcpy(&kda_copy, kda12, sizeof(kda_copy)); + kda_copy.kda_index = (kda12->kda12_enable ? + 0 : KDA_REMOVE_ALL); + + explicit_bzero(kda12, sizeof(*kda12)); + /* Kludge to pass kda_copy to kda in fallthrough. */ + data = (void *)&kda_copy; + } + /* FALLTHROUGH */ +#endif case DIOCSKERNELDUMP: { struct diocskerneldump_arg *kda; 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_ALL || + kda->kda_index == KDA_REMOVE_DEV || + kda->kda_index == KDA_REMOVE) { + error = dumper_remove(devtoname(dev), kda); + explicit_bzero(kda, sizeof(*kda)); break; } if (kda->kda_encryption != KERNELDUMP_ENC_NONE) { - if (kda->kda_encryptedkeysize <= 0 || + if (kda->kda_encryptedkeysize == 0 || kda->kda_encryptedkeysize > KERNELDUMP_ENCKEY_MAX_SIZE) { + explicit_bzero(kda, sizeof(*kda)); return (EINVAL); } encryptedkey = malloc(kda->kda_encryptedkeysize, M_TEMP, @@ -583,7 +607,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); @@ -859,8 +883,13 @@ g_trace(G_T_TOPOLOGY, "g_dev_orphan(%p(%s))", cp, cp->geom->name); /* Reset any dump-area set on this device */ - if (dev->si_flags & SI_DUMPDEV) - (void)clear_dumper(curthread); + if (dev->si_flags & SI_DUMPDEV) { + struct diocskerneldump_arg kda; + + bzero(&kda, sizeof(kda)); + kda.kda_index = KDA_REMOVE_DEV; + (void)dumper_remove(devtoname(dev), &kda); + } /* Destroy the struct cdev *so we get no more requests */ delist_dev(dev); Index: head/sys/geom/raid/g_raid.h =================================================================== --- head/sys/geom/raid/g_raid.h +++ head/sys/geom/raid/g_raid.h @@ -155,7 +155,6 @@ struct g_raid_softc *d_softc; /* Back-pointer to softc. */ struct g_consumer *d_consumer; /* GEOM disk consumer. */ void *d_md_data; /* Disk's metadata storage. */ - struct g_kerneldump d_kd; /* Kernel dumping method/args. */ int d_candelete; /* BIO_DELETE supported. */ uint64_t d_flags; /* Additional flags. */ u_int d_state; /* Disk state. */ @@ -164,6 +163,7 @@ int d_read_errs; /* Count of the read errors */ TAILQ_HEAD(, g_raid_subdisk) d_subdisks; /* List of subdisks. */ TAILQ_ENTRY(g_raid_disk) d_next; /* Next disk in the node. */ + struct g_kerneldump d_kd; /* Kernel dumping method/args. */ }; #define G_RAID_SUBDISK_S_NONE 0x00 /* Absent. */ Index: head/sys/kern/kern_shutdown.c =================================================================== --- head/sys/kern/kern_shutdown.c +++ head/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,8 +212,17 @@ int dumping; /* system is dumping */ int rebooting; /* system is rebooting */ -static struct dumperinfo dumper; /* our selected dumper */ +/* + * Used to serialize between sysctl kern.shutdown.dumpdevname and list + * modifications via ioctl. + */ +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. */ lwpid_t dumptid; /* Thread ID. */ @@ -364,7 +376,7 @@ error = 0; if (dumping) return (EBUSY); - if (dumper.dumper == NULL) + if (TAILQ_EMPTY(&dumper_configs)) return (ENXIO); savectx(&dumppcb); @@ -375,12 +387,19 @@ #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,10 +971,36 @@ 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[256]; + struct dumperinfo *di; + struct sbuf sb; + int error; + error = sysctl_wire_old_buffer(req, 0); + if (error != 0) + return (error); + + sbuf_new_for_sysctl(&sb, buf, sizeof(buf), req); + + mtx_lock(&dumpconf_list_lk); + TAILQ_FOREACH(di, &dumper_configs, di_next) { + if (di != TAILQ_FIRST(&dumper_configs)) + sbuf_putc(&sb, ','); + sbuf_cat(&sb, di->di_devname); + } + mtx_unlock(&dumpconf_list_lk); + + error = sbuf_finish(&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 +1137,67 @@ 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, - uint32_t encryptedkeysize, const uint8_t *encryptedkey) +dumper_insert(const struct dumperinfo *di_template, const char *devname, + const struct diocskerneldump_arg *kda) { - size_t wantcopy; + struct dumperinfo *newdi, *listdi; + bool inserted; + uint8_t index; int error; - error = priv_check(td, PRIV_SETDUMPER); + index = kda->kda_index; + MPASS(index != KDA_REMOVE && index != KDA_REMOVE_DEV && + index != KDA_REMOVE_ALL); + + 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); + memcpy(newdi, di_template, sizeof(*newdi)); + newdi->blockbuf = NULL; + newdi->kdcrypto = NULL; + newdi->kdcomp = NULL; + strcpy(newdi->di_devname, devname); - if (encryption != KERNELDUMP_ENC_NONE) { + if (kda->kda_encryption != KERNELDUMP_ENC_NONE) { #ifdef EKCD - dumper.kdcrypto = kerneldumpcrypto_create(di->blocksize, - encryption, key, encryptedkeysize, encryptedkey); - if (dumper.kdcrypto == NULL) { + newdi->kdcrypto = kerneldumpcrypto_create(di_template->blocksize, + kda->kda_encryption, kda->kda_key, + kda->kda_encryptedkeysize, kda->kda_encryptedkey); + if (newdi->kdcrypto == NULL) { error = EINVAL; goto cleanup; } @@ -1125,66 +1206,117 @@ 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) { + if (kda->kda_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) { + if (kda->kda_encryption != KERNELDUMP_ENC_NONE) { error = EOPNOTSUPP; goto cleanup; } - dumper.kdcomp = kerneldumpcomp_create(&dumper, compression); - if (dumper.kdcomp == NULL) { + newdi->kdcomp = kerneldumpcomp_create(newdi, + kda->kda_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; + if (kda->kda_index == KDA_REMOVE_ALL) + 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->kda_index == KDA_REMOVE_DEV) + 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); - kerneldumpcomp_destroy(&dumper); + return (true); +} - if (dumper.blockbuf != NULL) { - explicit_bzero(dumper.blockbuf, dumper.blocksize); - free(dumper.blockbuf, M_DUMPER); +int +dumper_remove(const char *devname, const struct diocskerneldump_arg *kda) +{ + struct dumperinfo *di, *sdi; + bool found; + int error; + + error = priv_check(curthread, PRIV_SETDUMPER); + if (error != 0) + return (error); + + /* + * 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); + + /* Only produce ENOENT if a more targeted match didn't match. */ + if (!found && kda->kda_index == KDA_REMOVE) + return (ENOENT); return (0); } Index: head/sys/netinet/netdump/netdump.h =================================================================== --- head/sys/netinet/netdump/netdump.h +++ head/sys/netinet/netdump/netdump.h @@ -60,18 +60,18 @@ uint32_t na_seqno; /* Match acks with msgs. */ } __packed; -struct netdump_conf { - struct diocskerneldump_arg ndc_kda; - char ndc_iface[IFNAMSIZ]; - struct in_addr ndc_server; - struct in_addr ndc_client; - struct in_addr ndc_gateway; +struct netdump_conf_freebsd12 { + struct diocskerneldump_arg_freebsd12 ndc12_kda; + char ndc12_iface[IFNAMSIZ]; + struct in_addr ndc12_server; + struct in_addr ndc12_client; + struct in_addr ndc12_gateway; }; -#define _PATH_NETDUMP "/dev/netdump" +#define NETDUMPGCONF_FREEBSD12 _IOR('n', 1, struct netdump_conf_freebsd12) +#define NETDUMPSCONF_FREEBSD12 _IOW('n', 2, struct netdump_conf_freebsd12) -#define NETDUMPGCONF _IOR('n', 1, struct netdump_conf) -#define NETDUMPSCONF _IOW('n', 2, struct netdump_conf) +#define _PATH_NETDUMP "/dev/netdump" #ifdef _KERNEL #ifdef NETDUMP Index: head/sys/netinet/netdump/netdump_client.c =================================================================== --- head/sys/netinet/netdump/netdump_client.c +++ head/sys/netinet/netdump/netdump_client.c @@ -89,7 +89,8 @@ static int netdump_arp_gw(void); static void netdump_cleanup(void); -static int netdump_configure(struct netdump_conf *, struct thread *); +static int netdump_configure(struct diocskerneldump_arg *, + struct thread *); static int netdump_dumper(void *priv __unused, void *virtual, vm_offset_t physical __unused, off_t offset, size_t length); static int netdump_ether_output(struct mbuf *m, struct ifnet *ifp, @@ -118,10 +119,10 @@ CTASSERT(sizeof(rcvd_acks) * NBBY == NETDUMP_MAX_IN_FLIGHT); /* Configuration parameters. */ -static struct netdump_conf nd_conf; -#define nd_server nd_conf.ndc_server -#define nd_client nd_conf.ndc_client -#define nd_gateway nd_conf.ndc_gateway +static struct diocskerneldump_arg nd_conf; +#define nd_server nd_conf.kda_server.in4 +#define nd_client nd_conf.kda_client.in4 +#define nd_gateway nd_conf.kda_gateway.in4 /* General dynamic settings. */ static struct ether_addr nd_gw_mac; @@ -1059,7 +1060,7 @@ static struct cdev *netdump_cdev; static int -netdump_configure(struct netdump_conf *conf, struct thread *td) +netdump_configure(struct diocskerneldump_arg *conf, struct thread *td) { struct epoch_tracker et; struct ifnet *ifp; @@ -1071,7 +1072,7 @@ } NET_EPOCH_ENTER(et); CK_STAILQ_FOREACH(ifp, &V_ifnet, if_link) { - if (strcmp(ifp->if_xname, conf->ndc_iface) == 0) + if (strcmp(ifp->if_xname, conf->kda_iface) == 0) break; } /* XXX ref */ @@ -1083,7 +1084,7 @@ if ((if_getflags(ifp) & IFF_UP) == 0) return (ENXIO); if (!netdump_supported_nic(ifp) || ifp->if_type != IFT_ETHER) - return (EINVAL); + return (ENODEV); nd_ifp = ifp; netdump_reinit(ifp); @@ -1135,19 +1136,24 @@ netdump_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t addr, int flags __unused, struct thread *td) { - struct diocskerneldump_arg *kda; + struct diocskerneldump_arg kda_copy, *conf; struct dumperinfo dumper; - struct netdump_conf *conf; uint8_t *encryptedkey; int error; #ifdef COMPAT_FREEBSD11 u_int u; #endif +#ifdef COMPAT_FREEBSD12 + struct diocskerneldump_arg_freebsd12 *kda12; + struct netdump_conf_freebsd12 *conf12; +#endif + conf = NULL; error = 0; switch (cmd) { #ifdef COMPAT_FREEBSD11 case DIOCSKERNELDUMP_FREEBSD11: + gone_in(13, "11.x ABI compatibility"); u = *(u_int *)addr; if (u != 0) { error = ENXIO; @@ -1159,9 +1165,17 @@ } break; #endif - case DIOCSKERNELDUMP: - kda = (void *)addr; - if (kda->kda_enable != 0) { +#ifdef COMPAT_FREEBSD12 + /* + * Used by dumpon(8) in 12.x for clearing previous + * configuration -- then NETDUMPSCONF_FREEBSD12 is used to + * actually configure netdump. + */ + case DIOCSKERNELDUMP_FREEBSD12: + gone_in(14, "12.x ABI compatibility"); + + kda12 = (void *)addr; + if (kda12->kda12_enable) { error = ENXIO; break; } @@ -1170,28 +1184,99 @@ netdump_mbuf_drain(); } break; - case NETDUMPGCONF: - conf = (struct netdump_conf *)addr; + + case NETDUMPGCONF_FREEBSD12: + gone_in(14, "FreeBSD 12.x ABI compat"); + conf12 = (void *)addr; + if (!nd_enabled) { error = ENXIO; break; } + if (nd_conf.kda_af != AF_INET) { + error = EOPNOTSUPP; + break; + } - strlcpy(conf->ndc_iface, nd_ifp->if_xname, - sizeof(conf->ndc_iface)); - memcpy(&conf->ndc_server, &nd_server, sizeof(nd_server)); - memcpy(&conf->ndc_client, &nd_client, sizeof(nd_client)); - memcpy(&conf->ndc_gateway, &nd_gateway, sizeof(nd_gateway)); + strlcpy(conf12->ndc12_iface, nd_ifp->if_xname, + sizeof(conf12->ndc12_iface)); + memcpy(&conf12->ndc12_server, &nd_server, + sizeof(conf12->ndc12_server)); + memcpy(&conf12->ndc12_client, &nd_client, + sizeof(conf12->ndc12_client)); + memcpy(&conf12->ndc12_gateway, &nd_gateway, + sizeof(conf12->ndc12_gateway)); break; - case NETDUMPSCONF: - conf = (struct netdump_conf *)addr; +#endif + case DIOCGKERNELDUMP: + conf = (void *)addr; + /* + * For now, index is ignored; netdump doesn't support multiple + * configurations (yet). + */ + if (!nd_enabled) { + error = ENXIO; + conf = NULL; + break; + } + + strlcpy(conf->kda_iface, nd_ifp->if_xname, + sizeof(conf->kda_iface)); + memcpy(&conf->kda_server, &nd_server, sizeof(nd_server)); + memcpy(&conf->kda_client, &nd_client, sizeof(nd_client)); + memcpy(&conf->kda_gateway, &nd_gateway, sizeof(nd_gateway)); + conf->kda_af = nd_conf.kda_af; + conf = NULL; + break; + +#ifdef COMPAT_FREEBSD12 + case NETDUMPSCONF_FREEBSD12: + gone_in(14, "FreeBSD 12.x ABI compat"); + + conf12 = (struct netdump_conf_freebsd12 *)addr; + + _Static_assert(offsetof(struct diocskerneldump_arg, kda_server) + == offsetof(struct netdump_conf_freebsd12, ndc12_server), + "simplifying assumption"); + + memset(&kda_copy, 0, sizeof(kda_copy)); + memcpy(&kda_copy, conf12, + offsetof(struct diocskerneldump_arg, kda_server)); + + /* 12.x ABI could only configure IPv4 (INET) netdump. */ + kda_copy.kda_af = AF_INET; + memcpy(&kda_copy.kda_server.in4, &conf12->ndc12_server, + sizeof(struct in_addr)); + memcpy(&kda_copy.kda_client.in4, &conf12->ndc12_client, + sizeof(struct in_addr)); + memcpy(&kda_copy.kda_gateway.in4, &conf12->ndc12_gateway, + sizeof(struct in_addr)); + + kda_copy.kda_index = + (conf12->ndc12_kda.kda12_enable ? 0 : KDA_REMOVE_ALL); + + conf = &kda_copy; + explicit_bzero(conf12, sizeof(*conf12)); + /* FALLTHROUGH */ +#endif + case DIOCSKERNELDUMP: encryptedkey = NULL; - kda = &conf->ndc_kda; + if (cmd == DIOCSKERNELDUMP) { + conf = (void *)addr; + memcpy(&kda_copy, conf, sizeof(kda_copy)); + } + /* Netdump only supports IP4 at this time. */ + if (conf->kda_af != AF_INET) { + error = EPROTONOSUPPORT; + break; + } - conf->ndc_iface[sizeof(conf->ndc_iface) - 1] = '\0'; - if (kda->kda_enable == 0) { - if (nd_enabled) { - error = clear_dumper(td); + conf->kda_iface[sizeof(conf->kda_iface) - 1] = '\0'; + if (conf->kda_index == KDA_REMOVE || + conf->kda_index == KDA_REMOVE_DEV || + conf->kda_index == KDA_REMOVE_ALL) { + if (nd_enabled || conf->kda_index == KDA_REMOVE_ALL) { + error = dumper_remove(conf->kda_iface, conf); if (error == 0) { nd_enabled = 0; netdump_mbuf_drain(); @@ -1204,19 +1289,23 @@ if (error != 0) break; - if (kda->kda_encryption != KERNELDUMP_ENC_NONE) { - if (kda->kda_encryptedkeysize <= 0 || - kda->kda_encryptedkeysize > - KERNELDUMP_ENCKEY_MAX_SIZE) - return (EINVAL); - encryptedkey = malloc(kda->kda_encryptedkeysize, M_TEMP, - M_WAITOK); - error = copyin(kda->kda_encryptedkey, encryptedkey, - kda->kda_encryptedkeysize); + if (conf->kda_encryption != KERNELDUMP_ENC_NONE) { + if (conf->kda_encryptedkeysize <= 0 || + conf->kda_encryptedkeysize > + KERNELDUMP_ENCKEY_MAX_SIZE) { + error = EINVAL; + break; + } + encryptedkey = malloc(conf->kda_encryptedkeysize, + M_TEMP, M_WAITOK); + error = copyin(conf->kda_encryptedkey, encryptedkey, + conf->kda_encryptedkeysize); if (error != 0) { free(encryptedkey, M_TEMP); - return (error); + break; } + + conf->kda_encryptedkey = encryptedkey; } memset(&dumper, 0, sizeof(dumper)); @@ -1229,12 +1318,10 @@ dumper.mediaoffset = 0; dumper.mediasize = 0; - error = set_dumper(&dumper, conf->ndc_iface, td, - kda->kda_compression, kda->kda_encryption, - kda->kda_key, kda->kda_encryptedkeysize, - encryptedkey); + error = dumper_insert(&dumper, conf->kda_iface, conf); if (encryptedkey != NULL) { - explicit_bzero(encryptedkey, kda->kda_encryptedkeysize); + explicit_bzero(encryptedkey, + conf->kda_encryptedkeysize); free(encryptedkey, M_TEMP); } if (error != 0) { @@ -1243,9 +1330,12 @@ } break; default: - error = EINVAL; + error = ENOTTY; break; } + explicit_bzero(&kda_copy, sizeof(kda_copy)); + if (conf != NULL) + explicit_bzero(conf, sizeof(*conf)); return (error); } @@ -1265,7 +1355,7 @@ static int netdump_modevent(module_t mod __unused, int what, void *priv __unused) { - struct netdump_conf conf; + struct diocskerneldump_arg conf; char *arg; int error; @@ -1278,33 +1368,41 @@ return (error); if ((arg = kern_getenv("net.dump.iface")) != NULL) { - strlcpy(conf.ndc_iface, arg, sizeof(conf.ndc_iface)); + strlcpy(conf.kda_iface, arg, sizeof(conf.kda_iface)); freeenv(arg); if ((arg = kern_getenv("net.dump.server")) != NULL) { - inet_aton(arg, &conf.ndc_server); + inet_aton(arg, &conf.kda_server.in4); freeenv(arg); } if ((arg = kern_getenv("net.dump.client")) != NULL) { - inet_aton(arg, &conf.ndc_server); + inet_aton(arg, &conf.kda_server.in4); freeenv(arg); } if ((arg = kern_getenv("net.dump.gateway")) != NULL) { - inet_aton(arg, &conf.ndc_server); + inet_aton(arg, &conf.kda_server.in4); freeenv(arg); } + conf.kda_af = AF_INET; /* Ignore errors; we print a message to the console. */ (void)netdump_configure(&conf, curthread); } break; case MOD_UNLOAD: - destroy_dev(netdump_cdev); if (nd_enabled) { + struct diocskerneldump_arg kda; + printf("netdump: disabling dump device for unload\n"); - (void)clear_dumper(curthread); + + bzero(&kda, sizeof(kda)); + kda.kda_index = KDA_REMOVE_DEV; + (void)dumper_remove(nd_conf.kda_iface, &kda); + + netdump_mbuf_drain(); nd_enabled = 0; } + destroy_dev(netdump_cdev); break; default: error = EOPNOTSUPP; Index: head/sys/sys/conf.h =================================================================== --- head/sys/sys/conf.h +++ head/sys/sys/conf.h @@ -352,15 +352,19 @@ 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, - uint32_t encryptedkeysize, const uint8_t *encryptedkey); -int clear_dumper(struct thread *td); +struct diocskerneldump_arg; +int dumper_insert(const struct dumperinfo *di_template, const char *devname, + const struct diocskerneldump_arg *kda); +int dumper_remove(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: head/sys/sys/disk.h =================================================================== --- head/sys/sys/disk.h +++ head/sys/sys/disk.h @@ -19,7 +19,11 @@ #include #include #include +#include +#include +#include + #ifdef _KERNEL #ifndef _SYS_CONF_H_ @@ -143,17 +147,66 @@ #define DIOCZONECMD _IOWR('d', 143, struct disk_zone_args) +struct diocskerneldump_arg_freebsd12 { + uint8_t kda12_enable; + uint8_t kda12_compression; + uint8_t kda12_encryption; + uint8_t kda12_key[KERNELDUMP_KEY_MAX_SIZE]; + uint32_t kda12_encryptedkeysize; + uint8_t *kda12_encryptedkey; +}; +#define DIOCSKERNELDUMP_FREEBSD12 \ + _IOW('d', 144, struct diocskerneldump_arg_freebsd12) + +union kd_ip { + struct in_addr in4; + struct in6_addr in6; +}; + +/* + * Sentinel values for kda_index. + * + * If kda_index is KDA_REMOVE_ALL, all dump configurations are cleared. + * + * If kda_index is KDA_REMOVE_DEV, all dump configurations for the specified + * device are cleared. + * + * If kda_index is KDA_REMOVE, only 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'. + */ +#define KDA_REMOVE UINT8_MAX +#define KDA_REMOVE_ALL (UINT8_MAX - 1) +#define KDA_REMOVE_DEV (UINT8_MAX - 2) +#define KDA_APPEND (UINT8_MAX - 3) 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]; uint32_t kda_encryptedkeysize; uint8_t *kda_encryptedkey; + char kda_iface[IFNAMSIZ]; + union kd_ip kda_server; + union kd_ip kda_client; + union kd_ip kda_gateway; + uint8_t kda_af; }; -#define DIOCSKERNELDUMP _IOW('d', 144, struct diocskerneldump_arg) +_Static_assert(__offsetof(struct diocskerneldump_arg, kda_iface) == + sizeof(struct diocskerneldump_arg_freebsd12), "simplifying assumption"); +#define DIOCSKERNELDUMP _IOW('d', 145, struct diocskerneldump_arg) /* * Enable/Disable the device for kernel core dumps. + */ + +#define DIOCGKERNELDUMP _IOWR('d', 146, struct diocskerneldump_arg) + /* + * Get current kernel netdump configuration details for a given index. */ #endif /* _SYS_DISK_H_ */ Index: head/sys/sys/param.h =================================================================== --- head/sys/sys/param.h +++ head/sys/sys/param.h @@ -60,7 +60,7 @@ * in the range 5 to 9. */ #undef __FreeBSD_version -#define __FreeBSD_version 1300022 /* Master, propagated to newvers */ +#define __FreeBSD_version 1300023 /* Master, propagated to newvers */ /* * __FreeBSD_kernel__ indicates that this system uses the kernel of FreeBSD,