1
0
mirror of https://git.FreeBSD.org/src.git synced 2024-12-23 11:18:54 +00:00

Add support for compressed kernel dumps.

When using a kernel built with the GZIO config option, dumpon -z can be
used to configure gzip compression using the in-kernel copy of zlib.
This is useful on systems with large amounts of RAM, which require a
correspondingly large dump device. Recovery of compressed dumps is also
faster since fewer bytes need to be copied from the dump device.

Because we have no way of knowing the final size of a compressed dump
until it is written, the kernel will always attempt to dump when
compression is configured, regardless of the dump device size. If the
dump is aborted because we run out of space, an error is reported on
the console.

savecore(8) is modified to handle compressed dumps and save them to
vmcore.<index>.gz, as it does when given the -z option.

A new rc.conf variable, dumpon_flags, is added. Its value is added to
the boot-time dumpon(8) invocation that occurs when a dump device is
configured in rc.conf.

Reviewed by:	cem (earlier version)
Discussed with:	def, rgrimes
Relnotes:	yes
Sponsored by:	Dell EMC Isilon
Differential Revision:	https://reviews.freebsd.org/D11723
This commit is contained in:
Mark Johnston 2017-10-25 00:51:00 +00:00
parent 7b79d6d61a
commit 64a16434d8
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=324965
15 changed files with 392 additions and 99 deletions

View File

@ -596,9 +596,8 @@ nscd_enable="NO" # Run the nsswitch caching daemon.
chkprintcap_enable="NO" # Run chkprintcap(8) before running lpd. chkprintcap_enable="NO" # Run chkprintcap(8) before running lpd.
chkprintcap_flags="-d" # Create missing directories by default. chkprintcap_flags="-d" # Create missing directories by default.
dumpdev="AUTO" # Device to crashdump to (device name, AUTO, or NO). dumpdev="AUTO" # Device to crashdump to (device name, AUTO, or NO).
dumpon_flags="" # Options to pass to dumpon(8), followed by dumpdev.
dumpdir="/var/crash" # Directory where crash dumps are to be stored dumpdir="/var/crash" # Directory where crash dumps are to be stored
dumppubkey="" # Public key for encrypted kernel crash dumps.
# See dumpon(8) for more details.
savecore_enable="YES" # Extract core from dump devices if any savecore_enable="YES" # Extract core from dump devices if any
savecore_flags="-m 10" # Used if dumpdev is enabled above, and present. savecore_flags="-m 10" # Used if dumpdev is enabled above, and present.
# By default, only the 10 most recent kernel dumps # By default, only the 10 most recent kernel dumps

View File

@ -16,11 +16,14 @@ stop_cmd="dumpon_stop"
dumpon_try() dumpon_try()
{ {
local flags
flags=${dumpon_flags}
if [ -n "${dumppubkey}" ]; then if [ -n "${dumppubkey}" ]; then
/sbin/dumpon -k "${dumppubkey}" "${1}" warn "The dumppubkey variable is deprecated. Use dumpon_flags."
else flags="${flags} -k ${dumppubkey}"
/sbin/dumpon "${1}"
fi fi
/sbin/dumpon ${flags} "${1}"
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
# Make a symlink in devfs for savecore # Make a symlink in devfs for savecore
ln -fs "${1}" /dev/dumpdev ln -fs "${1}" /dev/dumpdev

View File

@ -28,7 +28,7 @@
.\" From: @(#)swapon.8 8.1 (Berkeley) 6/5/93 .\" From: @(#)swapon.8 8.1 (Berkeley) 6/5/93
.\" $FreeBSD$ .\" $FreeBSD$
.\" .\"
.Dd December 10, 2016 .Dd October 24, 2017
.Dt DUMPON 8 .Dt DUMPON 8
.Os .Os
.Sh NAME .Sh NAME
@ -38,6 +38,7 @@
.Nm .Nm
.Op Fl v .Op Fl v
.Op Fl k Ar public_key_file .Op Fl k Ar public_key_file
.Op Fl z
.Ar special_file .Ar special_file
.Nm .Nm
.Op Fl v .Op Fl v
@ -114,6 +115,22 @@ This flag requires a kernel compiled with the
kernel option. kernel option.
.Pp .Pp
The The
.Fl z
option configures the kernel to compress the dump in gzip format before writing
it to the dump device.
This reduces the amount of space required for the dump and accelerates
recovery with
.Xr savecore 8
since less data needs to be copied from the dump device.
When compression is enabled, the
.Nm
utility will not verify that the dump device is sufficiently large for a full
dump.
This flag requires a kernel compiled with the
.Dv GZIO
kernel option.
.Pp
The
.Fl l .Fl l
flag causes flag causes
.Nm .Nm
@ -272,3 +289,7 @@ utility appeared in
.Sh BUGS .Sh BUGS
Because the file system layer is already dead by the time a crash dump Because the file system layer is already dead by the time a crash dump
is taken, it is not possible to send crash dumps directly to a file. is taken, it is not possible to send crash dumps directly to a file.
.Pp
It is currently not possible to configure both compression and encryption.
The encrypted dump format assumes that the kernel dump size is a multiple
of the cipher block size, which may not be true when the dump is compressed.

View File

@ -71,7 +71,7 @@ static void
usage(void) usage(void)
{ {
fprintf(stderr, "%s\n%s\n%s\n", fprintf(stderr, "%s\n%s\n%s\n",
"usage: dumpon [-v] [-k public_key_file] special_file", "usage: dumpon [-v] [-k public_key_file] [-z] special_file",
" dumpon [-v] off", " dumpon [-v] off",
" dumpon [-v] -l"); " dumpon [-v] -l");
exit(EX_USAGE); exit(EX_USAGE);
@ -190,11 +190,12 @@ main(int argc, char *argv[])
int ch; int ch;
int i, fd; int i, fd;
int do_listdumpdev = 0; int do_listdumpdev = 0;
bool enable; bool enable, gzip;
gzip = false;
pubkeyfile = NULL; pubkeyfile = NULL;
while ((ch = getopt(argc, argv, "k:lv")) != -1) while ((ch = getopt(argc, argv, "k:lvz")) != -1)
switch((char)ch) { switch((char)ch) {
case 'k': case 'k':
pubkeyfile = optarg; pubkeyfile = optarg;
@ -205,6 +206,9 @@ main(int argc, char *argv[])
case 'v': case 'v':
verbose = 1; verbose = 1;
break; break;
case 'z':
gzip = true;
break;
default: default:
usage(); usage();
} }
@ -247,9 +251,11 @@ main(int argc, char *argv[])
fd = open(dumpdev, O_RDONLY); fd = open(dumpdev, O_RDONLY);
if (fd < 0) if (fd < 0)
err(EX_OSFILE, "%s", dumpdev); err(EX_OSFILE, "%s", dumpdev);
check_size(fd, dumpdev);
bzero(&kda, sizeof(kda));
if (!gzip)
check_size(fd, dumpdev);
bzero(&kda, sizeof(kda));
kda.kda_enable = 0; kda.kda_enable = 0;
i = ioctl(fd, DIOCSKERNELDUMP, &kda); i = ioctl(fd, DIOCSKERNELDUMP, &kda);
explicit_bzero(&kda, sizeof(kda)); explicit_bzero(&kda, sizeof(kda));
@ -260,6 +266,8 @@ main(int argc, char *argv[])
#endif #endif
kda.kda_enable = 1; kda.kda_enable = 1;
kda.kda_compression = gzip ? KERNELDUMP_COMP_GZIP :
KERNELDUMP_COMP_NONE;
i = ioctl(fd, DIOCSKERNELDUMP, &kda); i = ioctl(fd, DIOCSKERNELDUMP, &kda);
explicit_bzero(kda.kda_encryptedkey, kda.kda_encryptedkeysize); explicit_bzero(kda.kda_encryptedkey, kda.kda_encryptedkeysize);
free(kda.kda_encryptedkey); free(kda.kda_encryptedkey);

View File

@ -28,7 +28,7 @@
.\" From: @(#)savecore.8 8.1 (Berkeley) 6/5/93 .\" From: @(#)savecore.8 8.1 (Berkeley) 6/5/93
.\" $FreeBSD$ .\" $FreeBSD$
.\" .\"
.Dd December 10, 2016 .Dd October 24, 2017
.Dt SAVECORE 8 .Dt SAVECORE 8
.Os .Os
.Sh NAME .Sh NAME
@ -96,8 +96,12 @@ the counter will restart from
Print out some additional debugging information. Print out some additional debugging information.
Specify twice for more information. Specify twice for more information.
.It Fl z .It Fl z
Compress the core dump and kernel (see Compress the dump (see
.Xr gzip 1 ) . .Xr gzip 1 ) .
The dump may already be compressed if the kernel was configured to
do so by
.Xr dumpon 8 .
In this case, the option has no effect.
.El .El
.Pp .Pp
The The

View File

@ -121,6 +121,9 @@ printheader(xo_handle_t *xo, const struct kerneldumpheader *h,
(long long)dumplen); (long long)dumplen);
xo_emit_h(xo, "{P: }{Lwc:Blocksize}{:blocksize/%d}\n", xo_emit_h(xo, "{P: }{Lwc:Blocksize}{:blocksize/%d}\n",
dtoh32(h->blocksize)); dtoh32(h->blocksize));
xo_emit_h(xo, "{P: }{Lwc:Compression}{:compression/%s}\n",
h->compression == KERNELDUMP_COMP_GZIP ?
"gzip" : "none");
t = dtoh64(h->dumptime); t = dtoh64(h->dumptime);
xo_emit_h(xo, "{P: }{Lwc:Dumptime}{:dumptime/%s}", ctime(&t)); xo_emit_h(xo, "{P: }{Lwc:Dumptime}{:dumptime/%s}", ctime(&t));
@ -357,7 +360,7 @@ compare_magic(const struct kerneldumpheader *kdh, const char *magic)
#define BLOCKMASK (~(BLOCKSIZE-1)) #define BLOCKMASK (~(BLOCKSIZE-1))
static int static int
DoRegularFile(int fd, bool isencrypted, off_t dumpsize, char *buf, DoRegularFile(int fd, off_t dumpsize, u_int sectorsize, bool sparse, char *buf,
const char *device, const char *filename, FILE *fp) const char *device, const char *filename, FILE *fp)
{ {
int he, hs, nr, nw, wl; int he, hs, nr, nw, wl;
@ -370,8 +373,8 @@ DoRegularFile(int fd, bool isencrypted, off_t dumpsize, char *buf,
wl = BUFFERSIZE; wl = BUFFERSIZE;
if (wl > dumpsize) if (wl > dumpsize)
wl = dumpsize; wl = dumpsize;
nr = read(fd, buf, wl); nr = read(fd, buf, roundup(wl, sectorsize));
if (nr != wl) { if (nr != (int)roundup(wl, sectorsize)) {
if (nr == 0) if (nr == 0)
syslog(LOG_WARNING, syslog(LOG_WARNING,
"WARNING: EOF on dump device"); "WARNING: EOF on dump device");
@ -380,7 +383,7 @@ DoRegularFile(int fd, bool isencrypted, off_t dumpsize, char *buf,
nerr++; nerr++;
return (-1); return (-1);
} }
if (compress || isencrypted) { if (!sparse) {
nw = fwrite(buf, 1, wl, fp); nw = fwrite(buf, 1, wl, fp);
} else { } else {
for (nw = 0; nw < nr; nw = he) { for (nw = 0; nw < nr; nw = he) {
@ -506,15 +509,14 @@ DoFile(const char *savedir, const char *device)
char *temp = NULL; char *temp = NULL;
struct kerneldumpheader kdhf, kdhl; struct kerneldumpheader kdhf, kdhl;
uint8_t *dumpkey; uint8_t *dumpkey;
off_t mediasize, dumpsize, firsthd, lasthd; off_t mediasize, dumpextent, dumplength, firsthd, lasthd;
FILE *info, *fp; FILE *info, *fp;
mode_t oumask; mode_t oumask;
int fd, fdinfo, error; int fd, fdinfo, error;
int bounds, status; int bounds, status;
u_int sectorsize, xostyle; u_int sectorsize, xostyle;
int istextdump;
uint32_t dumpkeysize; uint32_t dumpkeysize;
bool isencrypted, ret; bool iscompressed, isencrypted, istextdump, ret;
bounds = getbounds(); bounds = getbounds();
dumpkey = NULL; dumpkey = NULL;
@ -582,12 +584,12 @@ DoFile(const char *savedir, const char *device)
goto closefd; goto closefd;
} }
memcpy(&kdhl, temp, sizeof(kdhl)); memcpy(&kdhl, temp, sizeof(kdhl));
istextdump = 0; iscompressed = istextdump = false;
if (compare_magic(&kdhl, TEXTDUMPMAGIC)) { if (compare_magic(&kdhl, TEXTDUMPMAGIC)) {
if (verbose) if (verbose)
printf("textdump magic on last dump header on %s\n", printf("textdump magic on last dump header on %s\n",
device); device);
istextdump = 1; istextdump = true;
if (dtoh32(kdhl.version) != KERNELDUMP_TEXT_VERSION) { if (dtoh32(kdhl.version) != KERNELDUMP_TEXT_VERSION) {
syslog(LOG_ERR, syslog(LOG_ERR,
"unknown version (%d) in last dump header on %s", "unknown version (%d) in last dump header on %s",
@ -607,6 +609,20 @@ DoFile(const char *savedir, const char *device)
if (force == 0) if (force == 0)
goto closefd; goto closefd;
} }
switch (kdhl.compression) {
case KERNELDUMP_COMP_NONE:
break;
case KERNELDUMP_COMP_GZIP:
if (compress && verbose)
printf("dump is already compressed\n");
compress = false;
iscompressed = true;
break;
default:
syslog(LOG_ERR, "unknown compression type %d on %s",
kdhl.compression, device);
break;
}
} else { } else {
if (verbose) if (verbose)
printf("magic mismatch on last dump header on %s\n", printf("magic mismatch on last dump header on %s\n",
@ -619,8 +635,7 @@ DoFile(const char *savedir, const char *device)
if (compare_magic(&kdhl, KERNELDUMPMAGIC_CLEARED)) { if (compare_magic(&kdhl, KERNELDUMPMAGIC_CLEARED)) {
if (verbose) if (verbose)
printf("forcing magic on %s\n", device); printf("forcing magic on %s\n", device);
memcpy(kdhl.magic, KERNELDUMPMAGIC, memcpy(kdhl.magic, KERNELDUMPMAGIC, sizeof(kdhl.magic));
sizeof kdhl.magic);
} else { } else {
syslog(LOG_ERR, "unable to force dump - bad magic"); syslog(LOG_ERR, "unable to force dump - bad magic");
goto closefd; goto closefd;
@ -648,9 +663,10 @@ DoFile(const char *savedir, const char *device)
if (force == 0) if (force == 0)
goto closefd; goto closefd;
} }
dumpsize = dtoh64(kdhl.dumplength); dumpextent = dtoh64(kdhl.dumpextent);
dumplength = dtoh64(kdhl.dumplength);
dumpkeysize = dtoh32(kdhl.dumpkeysize); dumpkeysize = dtoh32(kdhl.dumpkeysize);
firsthd = lasthd - dumpsize - sectorsize - dumpkeysize; firsthd = lasthd - dumpextent - sectorsize - dumpkeysize;
if (lseek(fd, firsthd, SEEK_SET) != firsthd || if (lseek(fd, firsthd, SEEK_SET) != firsthd ||
read(fd, temp, sectorsize) != (ssize_t)sectorsize) { read(fd, temp, sectorsize) != (ssize_t)sectorsize) {
syslog(LOG_ERR, syslog(LOG_ERR,
@ -696,7 +712,7 @@ DoFile(const char *savedir, const char *device)
if (verbose) if (verbose)
printf("Checking for available free space\n"); printf("Checking for available free space\n");
if (!check_space(savedir, dumpsize, bounds)) { if (!check_space(savedir, dumplength, bounds)) {
nerr++; nerr++;
goto closefd; goto closefd;
} }
@ -724,6 +740,9 @@ DoFile(const char *savedir, const char *device)
istextdump ? "textdump.tar" : istextdump ? "textdump.tar" :
(isencrypted ? "vmcore_encrypted" : "vmcore"), bounds); (isencrypted ? "vmcore_encrypted" : "vmcore"), bounds);
fp = zopen(corename, "w"); fp = zopen(corename, "w");
} else if (iscompressed && !isencrypted) {
snprintf(corename, sizeof(corename), "vmcore.%d.gz", bounds);
fp = fopen(corename, "w");
} else { } else {
snprintf(corename, sizeof(corename), "%s.%d", snprintf(corename, sizeof(corename), "%s.%d",
istextdump ? "textdump.tar" : istextdump ? "textdump.tar" :
@ -792,11 +811,12 @@ DoFile(const char *savedir, const char *device)
savedir, corename); savedir, corename);
if (istextdump) { if (istextdump) {
if (DoTextdumpFile(fd, dumpsize, lasthd, buf, device, if (DoTextdumpFile(fd, dumplength, lasthd, buf, device,
corename, fp) < 0) corename, fp) < 0)
goto closeall; goto closeall;
} else { } else {
if (DoRegularFile(fd, isencrypted, dumpsize, buf, device, if (DoRegularFile(fd, dumplength, sectorsize,
!(compress || iscompressed || isencrypted), buf, device,
corename, fp) < 0) { corename, fp) < 0) {
goto closeall; goto closeall;
} }
@ -822,7 +842,7 @@ DoFile(const char *savedir, const char *device)
"key.last"); "key.last");
} }
} }
if (compress) { if (compress || iscompressed) {
snprintf(linkname, sizeof(linkname), "%s.last.gz", snprintf(linkname, sizeof(linkname), "%s.last.gz",
istextdump ? "textdump.tar" : istextdump ? "textdump.tar" :
(isencrypted ? "vmcore_encrypted" : "vmcore")); (isencrypted ? "vmcore_encrypted" : "vmcore"));

View File

@ -24,7 +24,7 @@
.\" .\"
.\" $FreeBSD$ .\" $FreeBSD$
.\" .\"
.Dd February 26, 2017 .Dd October 24, 2017
.Dt RC.CONF 5 .Dt RC.CONF 5
.Os .Os
.Sh NAME .Sh NAME
@ -3380,6 +3380,13 @@ Otherwise, the value of this variable is passed as the argument to
.Xr dumpon 8 . .Xr dumpon 8 .
To disable crash dumps, set this variable to To disable crash dumps, set this variable to
.Dq Li NO . .Dq Li NO .
.It Va dumpon_flags
.Pq Vt str
Flags to pass to
.Xr dumpon 8
when configuring
.Va dumpdev
as the system dump device.
.It Va dumpdir .It Va dumpdir
.Pq Vt str .Pq Vt str
When the system reboots after a crash and a crash dump is found on the When the system reboots after a crash and a crash dump is found on the
@ -3400,18 +3407,6 @@ to not run
at boot time when at boot time when
.Va dumpdir .Va dumpdir
is set. is set.
.It Va dumppubkey
.Pq Vt str
Path to a public key.
It is used by
.Xr dumpon 8
to encrypt a one-time key for a crash dump.
The public key has to match a private key used by
.Xr decryptcore 8
to decrypt a crash dump after reboot.
See
.Xr dumpon 8
for more details.
.It Va savecore_enable .It Va savecore_enable
.Pq Vt bool .Pq Vt bool
If set to If set to

View File

@ -114,7 +114,7 @@ null_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t data __unused,
case DIOCSKERNELDUMP_FREEBSD11: case DIOCSKERNELDUMP_FREEBSD11:
#endif #endif
case DIOCSKERNELDUMP: case DIOCSKERNELDUMP:
error = set_dumper(NULL, NULL, td, 0, NULL, 0, NULL); error = set_dumper(NULL, NULL, td, 0, 0, NULL, 0, NULL);
break; break;
case FIONBIO: case FIONBIO:
break; break;

View File

@ -138,7 +138,7 @@ g_dev_setdumpdev(struct cdev *dev, struct diocskerneldump_arg *kda,
int error, len; int error, len;
if (dev == NULL || kda == NULL) if (dev == NULL || kda == NULL)
return (set_dumper(NULL, NULL, td, 0, NULL, 0, NULL)); return (set_dumper(NULL, NULL, td, 0, 0, NULL, 0, NULL));
cp = dev->si_drv2; cp = dev->si_drv2;
len = sizeof(kd); len = sizeof(kd);
@ -148,8 +148,9 @@ g_dev_setdumpdev(struct cdev *dev, struct diocskerneldump_arg *kda,
if (error != 0) if (error != 0)
return (error); return (error);
error = set_dumper(&kd.di, devtoname(dev), td, kda->kda_encryption, error = set_dumper(&kd.di, devtoname(dev), td, kda->kda_compression,
kda->kda_key, kda->kda_encryptedkeysize, kda->kda_encryptedkey); kda->kda_encryption, kda->kda_key, kda->kda_encryptedkeysize,
kda->kda_encryptedkey);
if (error == 0) if (error == 0)
dev->si_flags |= SI_DUMPDEV; dev->si_flags |= SI_DUMPDEV;
@ -832,7 +833,7 @@ g_dev_orphan(struct g_consumer *cp)
/* Reset any dump-area set on this device */ /* Reset any dump-area set on this device */
if (dev->si_flags & SI_DUMPDEV) if (dev->si_flags & SI_DUMPDEV)
(void)set_dumper(NULL, NULL, curthread, 0, NULL, 0, NULL); (void)set_dumper(NULL, NULL, curthread, 0, 0, NULL, 0, NULL);
/* Destroy the struct cdev *so we get no more requests */ /* Destroy the struct cdev *so we get no more requests */
destroy_dev_sched_cb(dev, g_dev_callback, cp); destroy_dev_sched_cb(dev, g_dev_callback, cp);

View File

@ -60,7 +60,6 @@ struct gzio_stream *
gzio_init(gzio_cb cb, enum gzio_mode mode, size_t bufsz, int level, void *arg) gzio_init(gzio_cb cb, enum gzio_mode mode, size_t bufsz, int level, void *arg)
{ {
struct gzio_stream *s; struct gzio_stream *s;
uint8_t *hdr;
int error; int error;
if (bufsz < KERN_GZ_HDRLEN) if (bufsz < KERN_GZ_HDRLEN)
@ -72,7 +71,6 @@ gzio_init(gzio_cb cb, enum gzio_mode mode, size_t bufsz, int level, void *arg)
s->gz_bufsz = bufsz; s->gz_bufsz = bufsz;
s->gz_buffer = gz_alloc(NULL, 1, s->gz_bufsz); s->gz_buffer = gz_alloc(NULL, 1, s->gz_bufsz);
s->gz_mode = mode; s->gz_mode = mode;
s->gz_crc = ~0U;
s->gz_cb = cb; s->gz_cb = cb;
s->gz_arg = arg; s->gz_arg = arg;
@ -87,6 +85,26 @@ gzio_init(gzio_cb cb, enum gzio_mode mode, size_t bufsz, int level, void *arg)
if (error != 0) if (error != 0)
goto fail; goto fail;
gzio_reset(s);
return (s);
fail:
gz_free(NULL, s->gz_buffer);
gz_free(NULL, s);
return (NULL);
}
void
gzio_reset(struct gzio_stream *s)
{
uint8_t *hdr;
(void)deflateReset(&s->gz_stream);
s->gz_off = 0;
s->gz_crc = ~0U;
s->gz_stream.avail_out = s->gz_bufsz; s->gz_stream.avail_out = s->gz_bufsz;
s->gz_stream.next_out = s->gz_buffer; s->gz_stream.next_out = s->gz_buffer;
@ -99,13 +117,6 @@ gzio_init(gzio_cb cb, enum gzio_mode mode, size_t bufsz, int level, void *arg)
hdr[9] = OS_CODE; hdr[9] = OS_CODE;
s->gz_stream.next_out += KERN_GZ_HDRLEN; s->gz_stream.next_out += KERN_GZ_HDRLEN;
s->gz_stream.avail_out -= KERN_GZ_HDRLEN; s->gz_stream.avail_out -= KERN_GZ_HDRLEN;
return (s);
fail:
gz_free(NULL, s->gz_buffer);
gz_free(NULL, s);
return (NULL);
} }
int int

View File

@ -39,6 +39,7 @@ __FBSDID("$FreeBSD$");
#include "opt_ddb.h" #include "opt_ddb.h"
#include "opt_ekcd.h" #include "opt_ekcd.h"
#include "opt_gzio.h"
#include "opt_kdb.h" #include "opt_kdb.h"
#include "opt_panic.h" #include "opt_panic.h"
#include "opt_sched.h" #include "opt_sched.h"
@ -52,6 +53,7 @@ __FBSDID("$FreeBSD$");
#include <sys/cons.h> #include <sys/cons.h>
#include <sys/eventhandler.h> #include <sys/eventhandler.h>
#include <sys/filedesc.h> #include <sys/filedesc.h>
#include <sys/gzio.h>
#include <sys/jail.h> #include <sys/jail.h>
#include <sys/kdb.h> #include <sys/kdb.h>
#include <sys/kernel.h> #include <sys/kernel.h>
@ -162,6 +164,24 @@ struct kerneldumpcrypto {
}; };
#endif #endif
#ifdef GZIO
struct kerneldumpgz {
struct gzio_stream *kdgz_stream;
uint8_t *kdgz_buf;
size_t kdgz_resid;
};
static struct kerneldumpgz *kerneldumpgz_create(struct dumperinfo *di,
uint8_t compression);
static void kerneldumpgz_destroy(struct dumperinfo *di);
static int kerneldumpgz_write_cb(void *cb, size_t len, off_t off, void *arg);
static int kerneldump_gzlevel = 6;
SYSCTL_INT(_kern, OID_AUTO, kerneldump_gzlevel, CTLFLAG_RWTUN,
&kerneldump_gzlevel, 0,
"Kernel crash dump gzip compression level");
#endif /* GZIO */
/* /*
* Variable panicstr contains argument to first call to panic; used as flag * Variable panicstr contains argument to first call to panic; used as flag
* to indicate that the kernel has already called panic. * to indicate that the kernel has already called panic.
@ -857,6 +877,9 @@ static char dumpdevname[sizeof(((struct cdev*)NULL)->si_name)];
SYSCTL_STRING(_kern_shutdown, OID_AUTO, dumpdevname, CTLFLAG_RD, SYSCTL_STRING(_kern_shutdown, OID_AUTO, dumpdevname, CTLFLAG_RD,
dumpdevname, 0, "Device for kernel dumps"); dumpdevname, 0, "Device for kernel dumps");
static int _dump_append(struct dumperinfo *di, void *virtual,
vm_offset_t physical, size_t length);
#ifdef EKCD #ifdef EKCD
static struct kerneldumpcrypto * static struct kerneldumpcrypto *
kerneldumpcrypto_create(size_t blocksize, uint8_t encryption, kerneldumpcrypto_create(size_t blocksize, uint8_t encryption,
@ -947,11 +970,45 @@ kerneldumpcrypto_dumpkeysize(const struct kerneldumpcrypto *kdc)
} }
#endif /* EKCD */ #endif /* EKCD */
#ifdef GZIO
static struct kerneldumpgz *
kerneldumpgz_create(struct dumperinfo *di, uint8_t compression)
{
struct kerneldumpgz *kdgz;
if (compression != KERNELDUMP_COMP_GZIP)
return (NULL);
kdgz = malloc(sizeof(*kdgz), M_DUMPER, M_WAITOK | M_ZERO);
kdgz->kdgz_stream = gzio_init(kerneldumpgz_write_cb, GZIO_DEFLATE,
di->maxiosize, kerneldump_gzlevel, di);
if (kdgz->kdgz_stream == NULL) {
free(kdgz, M_DUMPER);
return (NULL);
}
kdgz->kdgz_buf = malloc(di->maxiosize, M_DUMPER, M_WAITOK | M_NODUMP);
return (kdgz);
}
static void
kerneldumpgz_destroy(struct dumperinfo *di)
{
struct kerneldumpgz *kdgz;
kdgz = di->kdgz;
if (kdgz == NULL)
return;
gzio_fini(kdgz->kdgz_stream);
explicit_bzero(kdgz->kdgz_buf, di->maxiosize);
free(kdgz->kdgz_buf, M_DUMPER);
free(kdgz, M_DUMPER);
}
#endif /* GZIO */
/* Registration of dumpers */ /* Registration of dumpers */
int int
set_dumper(struct dumperinfo *di, const char *devname, struct thread *td, set_dumper(struct dumperinfo *di, const char *devname, struct thread *td,
uint8_t encryption, const uint8_t *key, uint32_t encryptedkeysize, uint8_t compression, uint8_t encryption, const uint8_t *key,
const uint8_t *encryptedkey) uint32_t encryptedkeysize, const uint8_t *encryptedkey)
{ {
size_t wantcopy; size_t wantcopy;
int error; int error;
@ -969,6 +1026,7 @@ set_dumper(struct dumperinfo *di, const char *devname, struct thread *td,
dumper = *di; dumper = *di;
dumper.blockbuf = NULL; dumper.blockbuf = NULL;
dumper.kdc = NULL; dumper.kdc = NULL;
dumper.kdgz = NULL;
if (encryption != KERNELDUMP_ENC_NONE) { if (encryption != KERNELDUMP_ENC_NONE) {
#ifdef EKCD #ifdef EKCD
@ -987,7 +1045,28 @@ set_dumper(struct dumperinfo *di, const char *devname, struct thread *td,
wantcopy = strlcpy(dumpdevname, devname, sizeof(dumpdevname)); wantcopy = strlcpy(dumpdevname, devname, sizeof(dumpdevname));
if (wantcopy >= sizeof(dumpdevname)) { if (wantcopy >= sizeof(dumpdevname)) {
printf("set_dumper: device name truncated from '%s' -> '%s'\n", printf("set_dumper: device name truncated from '%s' -> '%s'\n",
devname, dumpdevname); devname, dumpdevname);
}
if (compression != KERNELDUMP_COMP_NONE) {
#ifdef GZIO
/*
* We currently can't support simultaneous encryption and
* compression.
*/
if (encryption != KERNELDUMP_ENC_NONE) {
error = EOPNOTSUPP;
goto cleanup;
}
dumper.kdgz = kerneldumpgz_create(&dumper, compression);
if (dumper.kdgz == NULL) {
error = EINVAL;
goto cleanup;
}
#else
error = EOPNOTSUPP;
goto cleanup;
#endif
} }
dumper.blockbuf = malloc(di->blocksize, M_DUMPER, M_WAITOK | M_ZERO); dumper.blockbuf = malloc(di->blocksize, M_DUMPER, M_WAITOK | M_ZERO);
@ -1000,6 +1079,11 @@ set_dumper(struct dumperinfo *di, const char *devname, struct thread *td,
free(dumper.kdc, M_EKCD); free(dumper.kdc, M_EKCD);
} }
#endif #endif
#ifdef GZIO
kerneldumpgz_destroy(&dumper);
#endif
if (dumper.blockbuf != NULL) { if (dumper.blockbuf != NULL) {
explicit_bzero(dumper.blockbuf, dumper.blocksize); explicit_bzero(dumper.blockbuf, dumper.blocksize);
free(dumper.blockbuf, M_DUMPER); free(dumper.blockbuf, M_DUMPER);
@ -1090,22 +1174,57 @@ dump_encrypted_write(struct dumperinfo *di, void *virtual,
} }
static int static int
dump_write_key(struct dumperinfo *di, vm_offset_t physical, off_t offset) dump_write_key(struct dumperinfo *di, off_t offset)
{ {
struct kerneldumpcrypto *kdc; struct kerneldumpcrypto *kdc;
kdc = di->kdc; kdc = di->kdc;
if (kdc == NULL) if (kdc == NULL)
return (0); return (0);
return (dump_write(di, kdc->kdc_dumpkey, 0, offset,
return (dump_write(di, kdc->kdc_dumpkey, physical, offset,
kdc->kdc_dumpkeysize)); kdc->kdc_dumpkeysize));
} }
#endif /* EKCD */ #endif /* EKCD */
#ifdef GZIO
static int
kerneldumpgz_write_cb(void *base, size_t length, off_t offset, void *arg)
{
struct dumperinfo *di;
size_t resid, rlength;
int error;
di = arg;
if (length % di->blocksize != 0) {
/*
* This must be the final write after flushing the compression
* stream. Write as many full blocks as possible and stash the
* residual data in the dumper's block buffer. It will be
* padded and written in dump_finish().
*/
rlength = rounddown(length, di->blocksize);
if (rlength != 0) {
error = _dump_append(di, base, 0, rlength);
if (error != 0)
return (error);
}
resid = length - rlength;
memmove(di->blockbuf, (uint8_t *)base + rlength, resid);
di->kdgz->kdgz_resid = resid;
return (EAGAIN);
}
return (_dump_append(di, base, 0, length));
}
#endif /* GZIO */
/*
* Write a kerneldumpheader at the specified offset. The header structure is 512
* bytes in size, but we must pad to the device sector size.
*/
static int static int
dump_write_header(struct dumperinfo *di, struct kerneldumpheader *kdh, dump_write_header(struct dumperinfo *di, struct kerneldumpheader *kdh,
vm_offset_t physical, off_t offset) off_t offset)
{ {
void *buf; void *buf;
size_t hdrsz; size_t hdrsz;
@ -1122,7 +1241,7 @@ dump_write_header(struct dumperinfo *di, struct kerneldumpheader *kdh,
memcpy(buf, kdh, hdrsz); memcpy(buf, kdh, hdrsz);
} }
return (dump_write(di, buf, physical, offset, di->blocksize)); return (dump_write(di, buf, 0, offset, di->blocksize));
} }
/* /*
@ -1132,19 +1251,30 @@ dump_write_header(struct dumperinfo *di, struct kerneldumpheader *kdh,
#define SIZEOF_METADATA (64 * 1024) #define SIZEOF_METADATA (64 * 1024)
/* /*
* Do some preliminary setup for a kernel dump: verify that we have enough space * Do some preliminary setup for a kernel dump: initialize state for encryption,
* on the dump device, write the leading header, and optionally write the crypto * if requested, and make sure that we have enough space on the dump device.
* key. *
* We set things up so that the dump ends before the last sector of the dump
* device, at which the trailing header is written.
*
* +-----------+------+-----+----------------------------+------+
* | | lhdr | key | ... kernel dump ... | thdr |
* +-----------+------+-----+----------------------------+------+
* 1 blk opt <------- dump extent --------> 1 blk
*
* Dumps written using dump_append() start at the beginning of the extent.
* Uncompressed dumps will use the entire extent, but compressed dumps typically
* will not. The true length of the dump is recorded in the leading and trailing
* headers once the dump has been completed.
*/ */
int int
dump_start(struct dumperinfo *di, struct kerneldumpheader *kdh) dump_start(struct dumperinfo *di, struct kerneldumpheader *kdh)
{ {
uint64_t dumpsize; uint64_t dumpextent;
uint32_t keysize; uint32_t keysize;
int error;
#ifdef EKCD #ifdef EKCD
error = kerneldumpcrypto_init(di->kdc); int error = kerneldumpcrypto_init(di->kdc);
if (error != 0) if (error != 0)
return (error); return (error);
keysize = kerneldumpcrypto_dumpkeysize(di->kdc); keysize = kerneldumpcrypto_dumpkeysize(di->kdc);
@ -1152,30 +1282,36 @@ dump_start(struct dumperinfo *di, struct kerneldumpheader *kdh)
keysize = 0; keysize = 0;
#endif #endif
dumpsize = dtoh64(kdh->dumplength) + 2 * di->blocksize + keysize; dumpextent = dtoh64(kdh->dumpextent);
if (di->mediasize < SIZEOF_METADATA + dumpsize) if (di->mediasize < SIZEOF_METADATA + dumpextent + 2 * di->blocksize +
return (E2BIG); keysize) {
#ifdef GZIO
di->dumpoff = di->mediaoffset + di->mediasize - dumpsize; if (di->kdgz != NULL) {
/*
error = dump_write_header(di, kdh, 0, di->dumpoff); * We don't yet know how much space the compressed dump
if (error != 0) * will occupy, so try to use the whole swap partition
return (error); * (minus the first 64KB) in the hope that the
di->dumpoff += di->blocksize; * compressed dump will fit. If that doesn't turn out to
* be enouch, the bounds checking in dump_write()
#ifdef EKCD * will catch us and cause the dump to fail.
error = dump_write_key(di, 0, di->dumpoff); */
if (error != 0) dumpextent = di->mediasize - SIZEOF_METADATA -
return (error); 2 * di->blocksize - keysize;
di->dumpoff += keysize; kdh->dumpextent = htod64(dumpextent);
} else
#endif #endif
return (E2BIG);
}
/* The offset at which to begin writing the dump. */
di->dumpoff = di->mediaoffset + di->mediasize - di->blocksize -
dumpextent;
return (0); return (0);
} }
/* Write to the dump device at the current dump offset. */ static int
int _dump_append(struct dumperinfo *di, void *virtual, vm_offset_t physical,
dump_append(struct dumperinfo *di, void *virtual, vm_offset_t physical,
size_t length) size_t length)
{ {
int error; int error;
@ -1192,7 +1328,33 @@ dump_append(struct dumperinfo *di, void *virtual, vm_offset_t physical,
return (error); return (error);
} }
/* Perform a raw write to the dump device at the specified offset. */ /*
* Write to the dump device starting at dumpoff. When compression is enabled,
* writes to the device will be performed using a callback that gets invoked
* when the compression stream's output buffer is full.
*/
int
dump_append(struct dumperinfo *di, void *virtual, vm_offset_t physical,
size_t length)
{
#ifdef GZIO
void *buf;
if (di->kdgz != NULL) {
/* Bounce through a buffer to avoid gzip CRC errors. */
if (length > di->maxiosize)
return (EINVAL);
buf = di->kdgz->kdgz_buf;
memmove(buf, virtual, length);
return (gzio_write(di->kdgz->kdgz_stream, buf, length));
}
#endif
return (_dump_append(di, virtual, physical, length));
}
/*
* Write to the dump device at the specified offset.
*/
int int
dump_write(struct dumperinfo *di, void *virtual, vm_offset_t physical, dump_write(struct dumperinfo *di, void *virtual, vm_offset_t physical,
off_t offset, size_t length) off_t offset, size_t length)
@ -1206,15 +1368,71 @@ dump_write(struct dumperinfo *di, void *virtual, vm_offset_t physical,
} }
/* /*
* Write the trailing kernel dump header and signal to the lower layers that the * Perform kernel dump finalization: flush the compression stream, if necessary,
* dump has completed. * write the leading and trailing kernel dump headers now that we know the true
* length of the dump, and optionally write the encryption key following the
* leading header.
*/ */
int int
dump_finish(struct dumperinfo *di, struct kerneldumpheader *kdh) dump_finish(struct dumperinfo *di, struct kerneldumpheader *kdh)
{ {
uint64_t extent;
uint32_t keysize;
int error; int error;
error = dump_write_header(di, kdh, 0, di->dumpoff); extent = dtoh64(kdh->dumpextent);
#ifdef EKCD
keysize = kerneldumpcrypto_dumpkeysize(di->kdc);
#else
keysize = 0;
#endif
#ifdef GZIO
if (di->kdgz != NULL) {
error = gzio_flush(di->kdgz->kdgz_stream);
if (error == EAGAIN) {
/* We have residual data in di->blockbuf. */
error = dump_write(di, di->blockbuf, 0, di->dumpoff,
di->blocksize);
di->dumpoff += di->kdgz->kdgz_resid;
di->kdgz->kdgz_resid = 0;
}
if (error != 0)
return (error);
/*
* We now know the size of the compressed dump, so update the
* header accordingly and recompute parity.
*/
kdh->dumplength = htod64(di->dumpoff -
(di->mediaoffset + di->mediasize - di->blocksize - extent));
kdh->parity = 0;
kdh->parity = kerneldump_parity(kdh);
gzio_reset(di->kdgz->kdgz_stream);
}
#endif
/*
* Write kerneldump headers at the beginning and end of the dump extent.
* Write the key after the leading header.
*/
error = dump_write_header(di, kdh,
di->mediaoffset + di->mediasize - 2 * di->blocksize - extent -
keysize);
if (error != 0)
return (error);
#ifdef EKCD
error = dump_write_key(di,
di->mediaoffset + di->mediasize - di->blocksize - extent - keysize);
if (error != 0)
return (error);
#endif
error = dump_write_header(di, kdh,
di->mediaoffset + di->mediasize - di->blocksize);
if (error != 0) if (error != 0)
return (error); return (error);
@ -1234,6 +1452,7 @@ dump_init_header(const struct dumperinfo *di, struct kerneldumpheader *kdh,
kdh->version = htod32(KERNELDUMPVERSION); kdh->version = htod32(KERNELDUMPVERSION);
kdh->architectureversion = htod32(archver); kdh->architectureversion = htod32(archver);
kdh->dumplength = htod64(dumplen); kdh->dumplength = htod64(dumplen);
kdh->dumpextent = kdh->dumplength;
kdh->dumptime = htod64(time_second); kdh->dumptime = htod64(time_second);
#ifdef EKCD #ifdef EKCD
kdh->dumpkeysize = htod32(kerneldumpcrypto_dumpkeysize(di->kdc)); kdh->dumpkeysize = htod32(kerneldumpcrypto_dumpkeysize(di->kdc));
@ -1247,6 +1466,10 @@ dump_init_header(const struct dumperinfo *di, struct kerneldumpheader *kdh,
kdh->versionstring[dstsize - 2] = '\n'; kdh->versionstring[dstsize - 2] = '\n';
if (panicstr != NULL) if (panicstr != NULL)
strlcpy(kdh->panicstring, panicstr, sizeof(kdh->panicstring)); strlcpy(kdh->panicstring, panicstr, sizeof(kdh->panicstring));
#ifdef GZIO
if (di->kdgz != NULL)
kdh->compression = KERNELDUMP_COMP_GZIP;
#endif
kdh->parity = kerneldump_parity(kdh); kdh->parity = kerneldump_parity(kdh);
} }

View File

@ -338,14 +338,15 @@ struct dumperinfo {
void *blockbuf; /* Buffer for padding shorter dump blocks */ void *blockbuf; /* Buffer for padding shorter dump blocks */
off_t dumpoff; /* Offset of ongoing kernel dump. */ off_t dumpoff; /* Offset of ongoing kernel dump. */
struct kerneldumpcrypto *kdc; /* Kernel dump crypto. */ struct kerneldumpcrypto *kdc; /* Kernel dump crypto. */
struct kerneldumpgz *kdgz; /* Kernel dump compression. */
}; };
extern int dumping; /* system is dumping */ extern int dumping; /* system is dumping */
int doadump(boolean_t); int doadump(boolean_t);
int set_dumper(struct dumperinfo *di, const char *devname, struct thread *td, int set_dumper(struct dumperinfo *di, const char *devname, struct thread *td,
uint8_t encrypt, const uint8_t *key, uint32_t encryptedkeysize, uint8_t compression, uint8_t encryption, const uint8_t *key,
const uint8_t *encryptedkey); uint32_t encryptedkeysize, const uint8_t *encryptedkey);
int dump_start(struct dumperinfo *di, struct kerneldumpheader *kdh); int dump_start(struct dumperinfo *di, struct kerneldumpheader *kdh);
int dump_append(struct dumperinfo *, void *, vm_offset_t, size_t); int dump_append(struct dumperinfo *, void *, vm_offset_t, size_t);

View File

@ -143,6 +143,7 @@ struct diocgattr_arg {
struct diocskerneldump_arg { struct diocskerneldump_arg {
uint8_t kda_enable; uint8_t kda_enable;
uint8_t kda_compression;
uint8_t kda_encryption; uint8_t kda_encryption;
uint8_t kda_key[KERNELDUMP_KEY_MAX_SIZE]; uint8_t kda_key[KERNELDUMP_KEY_MAX_SIZE];
uint32_t kda_encryptedkeysize; uint32_t kda_encryptedkeysize;

View File

@ -40,6 +40,7 @@ typedef int (*gzio_cb)(void *, size_t, off_t, void *);
struct gzio_stream; struct gzio_stream;
struct gzio_stream *gzio_init(gzio_cb cb, enum gzio_mode, size_t, int, void *); struct gzio_stream *gzio_init(gzio_cb cb, enum gzio_mode, size_t, int, void *);
void gzio_reset(struct gzio_stream *);
int gzio_write(struct gzio_stream *, void *, u_int); int gzio_write(struct gzio_stream *, void *, u_int);
int gzio_flush(struct gzio_stream *); int gzio_flush(struct gzio_stream *);
void gzio_fini(struct gzio_stream *); void gzio_fini(struct gzio_stream *);

View File

@ -55,6 +55,9 @@
#define htod64(x) (x) #define htod64(x) (x)
#endif #endif
#define KERNELDUMP_COMP_NONE 0
#define KERNELDUMP_COMP_GZIP 1
#define KERNELDUMP_ENC_NONE 0 #define KERNELDUMP_ENC_NONE 0
#define KERNELDUMP_ENC_AES_256_CBC 1 #define KERNELDUMP_ENC_AES_256_CBC 1
@ -75,8 +78,8 @@ struct kerneldumpheader {
#define KERNELDUMPMAGIC_CLEARED "Cleared Kernel Dump" #define KERNELDUMPMAGIC_CLEARED "Cleared Kernel Dump"
char architecture[12]; char architecture[12];
uint32_t version; uint32_t version;
#define KERNELDUMPVERSION 2 #define KERNELDUMPVERSION 3
#define KERNELDUMP_TEXT_VERSION 2 #define KERNELDUMP_TEXT_VERSION 3
uint32_t architectureversion; uint32_t architectureversion;
#define KERNELDUMP_AARCH64_VERSION 1 #define KERNELDUMP_AARCH64_VERSION 1
#define KERNELDUMP_AMD64_VERSION 2 #define KERNELDUMP_AMD64_VERSION 2
@ -87,12 +90,14 @@ struct kerneldumpheader {
#define KERNELDUMP_RISCV_VERSION 1 #define KERNELDUMP_RISCV_VERSION 1
#define KERNELDUMP_SPARC64_VERSION 1 #define KERNELDUMP_SPARC64_VERSION 1
uint64_t dumplength; /* excl headers */ uint64_t dumplength; /* excl headers */
uint64_t dumpextent;
uint64_t dumptime; uint64_t dumptime;
uint32_t dumpkeysize; uint32_t dumpkeysize;
uint32_t blocksize; uint32_t blocksize;
uint8_t compression;
char hostname[64]; char hostname[64];
char versionstring[192]; char versionstring[192];
char panicstring[188]; char panicstring[179];
uint32_t parity; uint32_t parity;
}; };