diff options
| author | Mark Johnston <markj@FreeBSD.org> | 2017-10-25 00:51:00 +0000 |
|---|---|---|
| committer | Mark Johnston <markj@FreeBSD.org> | 2017-10-25 00:51:00 +0000 |
| commit | 64a16434d8dbd5432e2f1a49007e53a449d43830 (patch) | |
| tree | e9cdb0a8780646580c9a0d35fd94332c6a2443ad /sys | |
| parent | 7b79d6d61ac82b21e2acc396a4ea66d0e92c79d9 (diff) | |
Notes
Diffstat (limited to 'sys')
| -rw-r--r-- | sys/dev/null/null.c | 2 | ||||
| -rw-r--r-- | sys/geom/geom_dev.c | 9 | ||||
| -rw-r--r-- | sys/kern/kern_gzio.c | 29 | ||||
| -rw-r--r-- | sys/kern/kern_shutdown.c | 297 | ||||
| -rw-r--r-- | sys/sys/conf.h | 5 | ||||
| -rw-r--r-- | sys/sys/disk.h | 1 | ||||
| -rw-r--r-- | sys/sys/gzio.h | 1 | ||||
| -rw-r--r-- | sys/sys/kerneldump.h | 11 |
8 files changed, 299 insertions, 56 deletions
diff --git a/sys/dev/null/null.c b/sys/dev/null/null.c index d946da6208ff..22654a251de4 100644 --- a/sys/dev/null/null.c +++ b/sys/dev/null/null.c @@ -114,7 +114,7 @@ null_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t data __unused, case DIOCSKERNELDUMP_FREEBSD11: #endif case DIOCSKERNELDUMP: - error = set_dumper(NULL, NULL, td, 0, NULL, 0, NULL); + error = set_dumper(NULL, NULL, td, 0, 0, NULL, 0, NULL); break; case FIONBIO: break; diff --git a/sys/geom/geom_dev.c b/sys/geom/geom_dev.c index 03a404682b1c..202c9f7dc0a4 100644 --- a/sys/geom/geom_dev.c +++ b/sys/geom/geom_dev.c @@ -138,7 +138,7 @@ g_dev_setdumpdev(struct cdev *dev, struct diocskerneldump_arg *kda, int error, len; 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; len = sizeof(kd); @@ -148,8 +148,9 @@ g_dev_setdumpdev(struct cdev *dev, struct diocskerneldump_arg *kda, if (error != 0) return (error); - error = set_dumper(&kd.di, devtoname(dev), td, kda->kda_encryption, - kda->kda_key, kda->kda_encryptedkeysize, kda->kda_encryptedkey); + error = set_dumper(&kd.di, devtoname(dev), td, kda->kda_compression, + kda->kda_encryption, kda->kda_key, kda->kda_encryptedkeysize, + kda->kda_encryptedkey); if (error == 0) dev->si_flags |= SI_DUMPDEV; @@ -832,7 +833,7 @@ g_dev_orphan(struct g_consumer *cp) /* Reset any dump-area set on this device */ 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_dev_sched_cb(dev, g_dev_callback, cp); diff --git a/sys/kern/kern_gzio.c b/sys/kern/kern_gzio.c index cee21f0655b1..f659b9bb7d7a 100644 --- a/sys/kern/kern_gzio.c +++ b/sys/kern/kern_gzio.c @@ -60,7 +60,6 @@ struct gzio_stream * gzio_init(gzio_cb cb, enum gzio_mode mode, size_t bufsz, int level, void *arg) { struct gzio_stream *s; - uint8_t *hdr; int error; 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_buffer = gz_alloc(NULL, 1, s->gz_bufsz); s->gz_mode = mode; - s->gz_crc = ~0U; s->gz_cb = cb; 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) 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.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; s->gz_stream.next_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 diff --git a/sys/kern/kern_shutdown.c b/sys/kern/kern_shutdown.c index 36c275809add..c73df509249e 100644 --- a/sys/kern/kern_shutdown.c +++ b/sys/kern/kern_shutdown.c @@ -39,6 +39,7 @@ __FBSDID("$FreeBSD$"); #include "opt_ddb.h" #include "opt_ekcd.h" +#include "opt_gzio.h" #include "opt_kdb.h" #include "opt_panic.h" #include "opt_sched.h" @@ -52,6 +53,7 @@ __FBSDID("$FreeBSD$"); #include <sys/cons.h> #include <sys/eventhandler.h> #include <sys/filedesc.h> +#include <sys/gzio.h> #include <sys/jail.h> #include <sys/kdb.h> #include <sys/kernel.h> @@ -162,6 +164,24 @@ struct kerneldumpcrypto { }; #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 * 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, dumpdevname, 0, "Device for kernel dumps"); +static int _dump_append(struct dumperinfo *di, void *virtual, + vm_offset_t physical, size_t length); + #ifdef EKCD static struct kerneldumpcrypto * kerneldumpcrypto_create(size_t blocksize, uint8_t encryption, @@ -947,11 +970,45 @@ kerneldumpcrypto_dumpkeysize(const struct kerneldumpcrypto *kdc) } #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 */ int set_dumper(struct dumperinfo *di, const char *devname, struct thread *td, - uint8_t encryption, const uint8_t *key, uint32_t encryptedkeysize, - const uint8_t *encryptedkey) + uint8_t compression, uint8_t encryption, const uint8_t *key, + uint32_t encryptedkeysize, const uint8_t *encryptedkey) { size_t wantcopy; int error; @@ -969,6 +1026,7 @@ set_dumper(struct dumperinfo *di, const char *devname, struct thread *td, dumper = *di; dumper.blockbuf = NULL; dumper.kdc = NULL; + dumper.kdgz = NULL; if (encryption != KERNELDUMP_ENC_NONE) { #ifdef EKCD @@ -987,7 +1045,28 @@ set_dumper(struct dumperinfo *di, const char *devname, struct thread *td, wantcopy = strlcpy(dumpdevname, devname, sizeof(dumpdevname)); if (wantcopy >= sizeof(dumpdevname)) { 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); @@ -1000,6 +1079,11 @@ cleanup: free(dumper.kdc, M_EKCD); } #endif + +#ifdef GZIO + kerneldumpgz_destroy(&dumper); +#endif + if (dumper.blockbuf != NULL) { explicit_bzero(dumper.blockbuf, dumper.blocksize); free(dumper.blockbuf, M_DUMPER); @@ -1090,22 +1174,57 @@ dump_encrypted_write(struct dumperinfo *di, void *virtual, } 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; kdc = di->kdc; if (kdc == NULL) return (0); - - return (dump_write(di, kdc->kdc_dumpkey, physical, offset, + return (dump_write(di, kdc->kdc_dumpkey, 0, offset, kdc->kdc_dumpkeysize)); } #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 dump_write_header(struct dumperinfo *di, struct kerneldumpheader *kdh, - vm_offset_t physical, off_t offset) + off_t offset) { void *buf; size_t hdrsz; @@ -1122,7 +1241,7 @@ dump_write_header(struct dumperinfo *di, struct kerneldumpheader *kdh, 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) /* - * Do some preliminary setup for a kernel dump: verify that we have enough space - * on the dump device, write the leading header, and optionally write the crypto - * key. + * Do some preliminary setup for a kernel dump: initialize state for encryption, + * if requested, and make sure that we have enough space on the dump device. + * + * 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 dump_start(struct dumperinfo *di, struct kerneldumpheader *kdh) { - uint64_t dumpsize; + uint64_t dumpextent; uint32_t keysize; - int error; #ifdef EKCD - error = kerneldumpcrypto_init(di->kdc); + int error = kerneldumpcrypto_init(di->kdc); if (error != 0) return (error); keysize = kerneldumpcrypto_dumpkeysize(di->kdc); @@ -1152,30 +1282,36 @@ dump_start(struct dumperinfo *di, struct kerneldumpheader *kdh) keysize = 0; #endif - dumpsize = dtoh64(kdh->dumplength) + 2 * di->blocksize + keysize; - if (di->mediasize < SIZEOF_METADATA + dumpsize) - return (E2BIG); - - di->dumpoff = di->mediaoffset + di->mediasize - dumpsize; - - error = dump_write_header(di, kdh, 0, di->dumpoff); - if (error != 0) - return (error); - di->dumpoff += di->blocksize; - -#ifdef EKCD - error = dump_write_key(di, 0, di->dumpoff); - if (error != 0) - return (error); - di->dumpoff += keysize; + dumpextent = dtoh64(kdh->dumpextent); + if (di->mediasize < SIZEOF_METADATA + dumpextent + 2 * di->blocksize + + keysize) { +#ifdef GZIO + if (di->kdgz != NULL) { + /* + * We don't yet know how much space the compressed dump + * will occupy, so try to use the whole swap partition + * (minus the first 64KB) in the hope that the + * compressed dump will fit. If that doesn't turn out to + * be enouch, the bounds checking in dump_write() + * will catch us and cause the dump to fail. + */ + dumpextent = di->mediasize - SIZEOF_METADATA - + 2 * di->blocksize - keysize; + kdh->dumpextent = htod64(dumpextent); + } else #endif + return (E2BIG); + } + + /* The offset at which to begin writing the dump. */ + di->dumpoff = di->mediaoffset + di->mediasize - di->blocksize - + dumpextent; return (0); } -/* Write to the dump device at the current dump offset. */ -int -dump_append(struct dumperinfo *di, void *virtual, vm_offset_t physical, +static int +_dump_append(struct dumperinfo *di, void *virtual, vm_offset_t physical, size_t length) { int error; @@ -1192,7 +1328,33 @@ dump_append(struct dumperinfo *di, void *virtual, vm_offset_t physical, 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 dump_write(struct dumperinfo *di, void *virtual, vm_offset_t physical, 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 - * dump has completed. + * Perform kernel dump finalization: flush the compression stream, if necessary, + * 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 dump_finish(struct dumperinfo *di, struct kerneldumpheader *kdh) { + uint64_t extent; + uint32_t keysize; 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) return (error); @@ -1234,6 +1452,7 @@ dump_init_header(const struct dumperinfo *di, struct kerneldumpheader *kdh, kdh->version = htod32(KERNELDUMPVERSION); kdh->architectureversion = htod32(archver); kdh->dumplength = htod64(dumplen); + kdh->dumpextent = kdh->dumplength; kdh->dumptime = htod64(time_second); #ifdef EKCD 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'; if (panicstr != NULL) strlcpy(kdh->panicstring, panicstr, sizeof(kdh->panicstring)); +#ifdef GZIO + if (di->kdgz != NULL) + kdh->compression = KERNELDUMP_COMP_GZIP; +#endif kdh->parity = kerneldump_parity(kdh); } diff --git a/sys/sys/conf.h b/sys/sys/conf.h index 4c33ec34a9b6..90e2afe20529 100644 --- a/sys/sys/conf.h +++ b/sys/sys/conf.h @@ -338,14 +338,15 @@ struct dumperinfo { void *blockbuf; /* Buffer for padding shorter dump blocks */ off_t dumpoff; /* Offset of ongoing kernel dump. */ struct kerneldumpcrypto *kdc; /* Kernel dump crypto. */ + struct kerneldumpgz *kdgz; /* Kernel dump compression. */ }; extern int dumping; /* system is dumping */ int doadump(boolean_t); int set_dumper(struct dumperinfo *di, const char *devname, struct thread *td, - uint8_t encrypt, const uint8_t *key, uint32_t encryptedkeysize, - const uint8_t *encryptedkey); + uint8_t compression, uint8_t encryption, const uint8_t *key, + uint32_t encryptedkeysize, const uint8_t *encryptedkey); int dump_start(struct dumperinfo *di, struct kerneldumpheader *kdh); int dump_append(struct dumperinfo *, void *, vm_offset_t, size_t); diff --git a/sys/sys/disk.h b/sys/sys/disk.h index 5c65eb177889..4ab0b09e12cd 100644 --- a/sys/sys/disk.h +++ b/sys/sys/disk.h @@ -143,6 +143,7 @@ struct diocgattr_arg { struct diocskerneldump_arg { uint8_t kda_enable; + uint8_t kda_compression; uint8_t kda_encryption; uint8_t kda_key[KERNELDUMP_KEY_MAX_SIZE]; uint32_t kda_encryptedkeysize; diff --git a/sys/sys/gzio.h b/sys/sys/gzio.h index c61c2818f2e9..524d80238fac 100644 --- a/sys/sys/gzio.h +++ b/sys/sys/gzio.h @@ -40,6 +40,7 @@ typedef int (*gzio_cb)(void *, size_t, off_t, void *); struct gzio_stream; 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_flush(struct gzio_stream *); void gzio_fini(struct gzio_stream *); diff --git a/sys/sys/kerneldump.h b/sys/sys/kerneldump.h index afd762923a57..864640ae5ff1 100644 --- a/sys/sys/kerneldump.h +++ b/sys/sys/kerneldump.h @@ -55,6 +55,9 @@ #define htod64(x) (x) #endif +#define KERNELDUMP_COMP_NONE 0 +#define KERNELDUMP_COMP_GZIP 1 + #define KERNELDUMP_ENC_NONE 0 #define KERNELDUMP_ENC_AES_256_CBC 1 @@ -75,8 +78,8 @@ struct kerneldumpheader { #define KERNELDUMPMAGIC_CLEARED "Cleared Kernel Dump" char architecture[12]; uint32_t version; -#define KERNELDUMPVERSION 2 -#define KERNELDUMP_TEXT_VERSION 2 +#define KERNELDUMPVERSION 3 +#define KERNELDUMP_TEXT_VERSION 3 uint32_t architectureversion; #define KERNELDUMP_AARCH64_VERSION 1 #define KERNELDUMP_AMD64_VERSION 2 @@ -87,12 +90,14 @@ struct kerneldumpheader { #define KERNELDUMP_RISCV_VERSION 1 #define KERNELDUMP_SPARC64_VERSION 1 uint64_t dumplength; /* excl headers */ + uint64_t dumpextent; uint64_t dumptime; uint32_t dumpkeysize; uint32_t blocksize; + uint8_t compression; char hostname[64]; char versionstring[192]; - char panicstring[188]; + char panicstring[179]; uint32_t parity; }; |
