From 480f31c214725f732a6d0e8ef4cd7c56ff98e155 Mon Sep 17 00:00:00 2001 From: Konrad Witaszczyk Date: Sat, 10 Dec 2016 16:20:39 +0000 Subject: [PATCH] Add support for encrypted kernel crash dumps. Changes include modifications in kernel crash dump routines, dumpon(8) and savecore(8). A new tool called decryptcore(8) was added. A new DIOCSKERNELDUMP I/O control was added to send a kernel crash dump configuration in the diocskerneldump_arg structure to the kernel. The old DIOCSKERNELDUMP I/O control was renamed to DIOCSKERNELDUMP_FREEBSD11 for backward ABI compatibility. dumpon(8) generates an one-time random symmetric key and encrypts it using an RSA public key in capability mode. Currently only AES-256-CBC is supported but EKCD was designed to implement support for other algorithms in the future. The public key is chosen using the -k flag. The dumpon rc(8) script can do this automatically during startup using the dumppubkey rc.conf(5) variable. Once the keys are calculated dumpon sends them to the kernel via DIOCSKERNELDUMP I/O control. When the kernel receives the DIOCSKERNELDUMP I/O control it generates a random IV and sets up the key schedule for the specified algorithm. Each time the kernel tries to write a crash dump to the dump device, the IV is replaced by a SHA-256 hash of the previous value. This is intended to make a possible differential cryptanalysis harder since it is possible to write multiple crash dumps without reboot by repeating the following commands: # sysctl debug.kdb.enter=1 db> call doadump(0) db> continue # savecore A kernel dump key consists of an algorithm identifier, an IV and an encrypted symmetric key. The kernel dump key size is included in a kernel dump header. The size is an unsigned 32-bit integer and it is aligned to a block size. The header structure has 512 bytes to match the block size so it was required to make a panic string 4 bytes shorter to add a new field to the header structure. If the kernel dump key size in the header is nonzero it is assumed that the kernel dump key is placed after the first header on the dump device and the core dump is encrypted. Separate functions were implemented to write the kernel dump header and the kernel dump key as they need to be unencrypted. The dump_write function encrypts data if the kernel was compiled with the EKCD option. Encrypted kernel textdumps are not supported due to the way they are constructed which makes it impossible to use the CBC mode for encryption. It should be also noted that textdumps don't contain sensitive data by design as a user decides what information should be dumped. savecore(8) writes the kernel dump key to a key.# file if its size in the header is nonzero. # is the number of the current core dump. decryptcore(8) decrypts the core dump using a private RSA key and the kernel dump key. This is performed by a child process in capability mode. If the decryption was not successful the parent process removes a partially decrypted core dump. Description on how to encrypt crash dumps was added to the decryptcore(8), dumpon(8), rc.conf(5) and savecore(8) manual pages. EKCD was tested on amd64 using bhyve and i386, mipsel and sparc64 using QEMU. The feature still has to be tested on arm and arm64 as it wasn't possible to run FreeBSD due to the problems with QEMU emulation and lack of hardware. Designed by: def, pjd Reviewed by: cem, oshogbo, pjd Partial review: delphij, emaste, jhb, kib Approved by: pjd (mentor) Differential Revision: https://reviews.freebsd.org/D4712 --- etc/defaults/rc.conf | 2 + etc/rc.d/dumpon | 7 +- sbin/Makefile | 1 + sbin/decryptcore/Makefile | 13 + sbin/decryptcore/decryptcore.8 | 114 +++++++++ sbin/decryptcore/decryptcore.c | 373 +++++++++++++++++++++++++++++ sbin/dumpon/Makefile | 8 + sbin/dumpon/dumpon.8 | 118 ++++++++- sbin/dumpon/dumpon.c | 111 ++++++++- sbin/savecore/savecore.8 | 7 +- sbin/savecore/savecore.c | 92 ++++++- share/man/man5/rc.conf.5 | 14 +- sys/amd64/amd64/minidump_machdep.c | 27 ++- sys/arm/arm/minidump_machdep.c | 27 ++- sys/arm64/arm64/minidump_machdep.c | 27 ++- sys/conf/NOTES | 3 + sys/conf/files | 6 +- sys/conf/options | 3 + sys/ddb/db_textdump.c | 17 +- sys/dev/null/null.c | 7 +- sys/geom/geom_dev.c | 82 +++++-- sys/i386/i386/minidump_machdep.c | 28 ++- sys/kern/kern_dump.c | 53 +++- sys/kern/kern_shutdown.c | 362 ++++++++++++++++++++++++++-- sys/mips/mips/minidump_machdep.c | 27 ++- sys/sparc64/sparc64/dump_machdep.c | 23 +- sys/sys/conf.h | 10 +- sys/sys/disk.h | 15 +- sys/sys/kerneldump.h | 31 ++- 29 files changed, 1485 insertions(+), 123 deletions(-) create mode 100644 sbin/decryptcore/Makefile create mode 100644 sbin/decryptcore/decryptcore.8 create mode 100644 sbin/decryptcore/decryptcore.c diff --git a/etc/defaults/rc.conf b/etc/defaults/rc.conf index f4fa7a8846df..c8a3061b62ff 100644 --- a/etc/defaults/rc.conf +++ b/etc/defaults/rc.conf @@ -607,6 +607,8 @@ chkprintcap_enable="NO" # Run chkprintcap(8) before running lpd. chkprintcap_flags="-d" # Create missing directories by default. dumpdev="AUTO" # Device to crashdump to (device name, AUTO, or NO). 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_flags="-m 10" # Used if dumpdev is enabled above, and present. # By default, only the 10 most recent kernel dumps diff --git a/etc/rc.d/dumpon b/etc/rc.d/dumpon index 66276eb50535..87bdd3eb0b39 100755 --- a/etc/rc.d/dumpon +++ b/etc/rc.d/dumpon @@ -16,7 +16,12 @@ stop_cmd="dumpon_stop" dumpon_try() { - if /sbin/dumpon "${1}" ; then + if [ -n "${dumppubkey}" ]; then + /sbin/dumpon -k "${dumppubkey}" "${1}" + else + /sbin/dumpon "${1}" + fi + if [ $? -eq 0 ]; then # Make a symlink in devfs for savecore ln -fs "${1}" /dev/dumpdev return 0 diff --git a/sbin/Makefile b/sbin/Makefile index 4826bc09b26d..39e417acbacf 100644 --- a/sbin/Makefile +++ b/sbin/Makefile @@ -83,6 +83,7 @@ SUBDIR.${MK_IPFW}+= natd SUBDIR.${MK_ISCSI}+= iscontrol SUBDIR.${MK_NAND}+= nandfs SUBDIR.${MK_NAND}+= newfs_nandfs +SUBDIR.${MK_OPENSSL}+= decryptcore SUBDIR.${MK_PF}+= pfctl SUBDIR.${MK_PF}+= pflogd SUBDIR.${MK_QUOTAS}+= quotacheck diff --git a/sbin/decryptcore/Makefile b/sbin/decryptcore/Makefile new file mode 100644 index 000000000000..be8b6d1c5934 --- /dev/null +++ b/sbin/decryptcore/Makefile @@ -0,0 +1,13 @@ +# $FreeBSD$ + +PROG= decryptcore + +LIBADD= crypto pjdlog + +MAN= decryptcore.8 + +CFLAGS+=-I${.CURDIR}/../../lib/libpjdlog + +WARNS?= 6 + +.include diff --git a/sbin/decryptcore/decryptcore.8 b/sbin/decryptcore/decryptcore.8 new file mode 100644 index 000000000000..d21667c0784f --- /dev/null +++ b/sbin/decryptcore/decryptcore.8 @@ -0,0 +1,114 @@ +.\" Copyright (c) 2016 Konrad Witaszczyk +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd December 10, 2016 +.Dt DECRYPTCORE 8 +.Os +.Sh NAME +.Nm decryptcore +.Nd "decrypt a core dump of the operating system" +.Sh SYNOPSIS +.Nm +.Op Fl Lv +.Fl p Ar privatekeyfile +.Fl k Ar keyfile +.Fl e Ar encryptedcore +.Fl c Ar core +.Nm +.Op Fl Lv +.Op Fl d Ar crashdir +.Fl p Ar privatekeyfile +.Fl n Ar dumpnr +.Sh DESCRIPTION +The +.Nm +first decrypts +.Ar keyfile +using +.Ar privatekeyfile +and then uses the resulting key to decrypt +.Ar encryptedcore +saved by +.Xr savecore 8 . +Result is saved in +.Ar core . +.Pp +Alternatively a user can decrypt a core dump numbered +.Ar dumpnr +from the +.Ar crashdir +directory. +In this case a dump key from the +.Pa key.# +file is used and the result is saved in the +.Pa vmcore.# +file where +.Dq # +corresponds to +.Ar dumpnr . +.Pp +The +.Nm +utility can be started with the following command line arguments: +.Bl -tag -width ".Fl e Ar encryptedcore" +.It Fl L +Write log messages to +.Xr syslogd 8 . +.It Fl v +Print or log verbose/debugging information. +This option can be specified multiple times to raise the verbosity +level. +.It Fl p Ar privatekeyfile +Specify location of a private key file which will be used to decrypt a dump key +file. +.It Fl k Ar keyfile +Specify location of a dump key file. +.It Fl e Ar encrytpedcore +Specify location of an encrypted core. +.It Fl c Ar core +Specify location of a resulting decrypted core dump. +.It Fl d Ar crashdir +Specify an alternative crash dump directory. The default crash dump directory is +.Pa /var/crash . +.It Fl n Ar dumpnr +Specify a number of a crash dump to be decrypted. +.El +.Sh EXIT STATUS +The +.Nm +utility exits 0 on success, and >0 if an error occurs. +.Sh SEE ALSO +.Xr capsicum 4 , +.Xr dumpon 8 , +.Xr kgdb 1 , +.Xr savecore 8 , +.Xr syslogd 8 +.Sh AUTHORS +The +.Nm +was implemented by +.An -nosplit +.An Konrad Witaszczyk Aq Mt def@FreeBSD.org . diff --git a/sbin/decryptcore/decryptcore.c b/sbin/decryptcore/decryptcore.c new file mode 100644 index 000000000000..758e4c8c0fea --- /dev/null +++ b/sbin/decryptcore/decryptcore.c @@ -0,0 +1,373 @@ +/*- + * Copyright (c) 2016 Konrad Witaszczyk + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "pjdlog.h" + +#define DECRYPTCORE_CRASHDIR "/var/crash" + +static void +usage(void) +{ + + pjdlog_exitx(1, + "usage: decryptcore [-Lv] -p privatekeyfile -k keyfile -e encryptedcore -c core\n" + " decryptcore [-Lv] [-d crashdir] -p privatekeyfile -n dumpnr"); +} + +static int +wait_for_process(pid_t pid) +{ + int status; + + if (waitpid(pid, &status, WUNTRACED | WEXITED) == -1) { + pjdlog_errno(LOG_ERR, "Unable to wait for a child process"); + return (1); + } + + if (WIFEXITED(status)) + return (WEXITSTATUS(status)); + + return (1); +} + +static struct kerneldumpkey * +read_key(int kfd) +{ + struct kerneldumpkey *kdk; + ssize_t size; + size_t kdksize; + + PJDLOG_ASSERT(kfd >= 0); + + kdksize = sizeof(*kdk); + kdk = calloc(1, kdksize); + if (kdk == NULL) { + pjdlog_errno(LOG_ERR, "Unable to allocate kernel dump key"); + goto failed; + } + + size = read(kfd, kdk, kdksize); + if (size == (ssize_t)kdksize) { + kdk->kdk_encryptedkeysize = dtoh32(kdk->kdk_encryptedkeysize); + kdksize += (size_t)kdk->kdk_encryptedkeysize; + kdk = realloc(kdk, kdksize); + if (kdk == NULL) { + pjdlog_errno(LOG_ERR, "Unable to reallocate kernel dump key"); + goto failed; + } + size += read(kfd, &kdk->kdk_encryptedkey, + kdk->kdk_encryptedkeysize); + } + if (size != (ssize_t)kdksize) { + pjdlog_errno(LOG_ERR, "Unable to read key"); + goto failed; + } + + return (kdk); +failed: + free(kdk); + return (NULL); +} + +static bool +decrypt(const char *privkeyfile, const char *keyfile, const char *input, + const char *output) +{ + uint8_t buf[KERNELDUMP_BUFFER_SIZE], key[KERNELDUMP_KEY_MAX_SIZE]; + EVP_CIPHER_CTX ctx; + const EVP_CIPHER *cipher; + FILE *fp; + struct kerneldumpkey *kdk; + RSA *privkey; + int ifd, kfd, ofd, olen, privkeysize; + ssize_t bytes; + pid_t pid; + + PJDLOG_ASSERT(privkeyfile != NULL); + PJDLOG_ASSERT(keyfile != NULL); + PJDLOG_ASSERT(input != NULL); + PJDLOG_ASSERT(output != NULL); + + privkey = NULL; + + /* + * Decrypt a core dump in a child process so we can unlink a partially + * decrypted core if the child process fails. + */ + pid = fork(); + if (pid == -1) { + pjdlog_errno(LOG_ERR, "Unable to create child process"); + return (false); + } + + if (pid > 0) + return (wait_for_process(pid) == 0); + + kfd = open(keyfile, O_RDONLY); + if (kfd == -1) { + pjdlog_errno(LOG_ERR, "Unable to open %s", keyfile); + goto failed; + } + ifd = open(input, O_RDONLY); + if (ifd == -1) { + pjdlog_errno(LOG_ERR, "Unable to open %s", input); + goto failed; + } + ofd = open(output, O_WRONLY | O_CREAT | O_TRUNC, 0600); + if (ofd == -1) { + pjdlog_errno(LOG_ERR, "Unable to open %s", output); + goto failed; + } + fp = fopen(privkeyfile, "r"); + if (fp == NULL) { + pjdlog_errno(LOG_ERR, "Unable to open %s", privkeyfile); + goto failed; + } + + if (cap_enter() < 0 && errno != ENOSYS) { + pjdlog_errno(LOG_ERR, "Unable to enter capability mode"); + goto failed; + } + + privkey = RSA_new(); + if (privkey == NULL) { + pjdlog_error("Unable to allocate an RSA structure: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto failed; + } + EVP_CIPHER_CTX_init(&ctx); + + kdk = read_key(kfd); + close(kfd); + if (kdk == NULL) + goto failed; + + privkey = PEM_read_RSAPrivateKey(fp, &privkey, NULL, NULL); + fclose(fp); + if (privkey == NULL) { + pjdlog_error("Unable to read data from %s.", privkeyfile); + goto failed; + } + + privkeysize = RSA_size(privkey); + if (privkeysize != (int)kdk->kdk_encryptedkeysize) { + pjdlog_error("RSA modulus size mismatch: equals %db and should be %ub.", + 8 * privkeysize, 8 * kdk->kdk_encryptedkeysize); + goto failed; + } + + switch (kdk->kdk_encryption) { + case KERNELDUMP_ENC_AES_256_CBC: + cipher = EVP_aes_256_cbc(); + break; + default: + pjdlog_error("Invalid encryption algorithm."); + goto failed; + } + + if (RSA_private_decrypt(kdk->kdk_encryptedkeysize, + kdk->kdk_encryptedkey, key, privkey, + RSA_PKCS1_PADDING) != sizeof(key)) { + pjdlog_error("Unable to decrypt key: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto failed; + } + RSA_free(privkey); + privkey = NULL; + + EVP_DecryptInit_ex(&ctx, cipher, NULL, key, kdk->kdk_iv); + EVP_CIPHER_CTX_set_padding(&ctx, 0); + + explicit_bzero(key, sizeof(key)); + + do { + bytes = read(ifd, buf, sizeof(buf)); + if (bytes < 0) { + pjdlog_errno(LOG_ERR, "Unable to read data from %s", + input); + goto failed; + } else if (bytes == 0) { + break; + } + + if (bytes > 0) { + if (EVP_DecryptUpdate(&ctx, buf, &olen, buf, + bytes) == 0) { + pjdlog_error("Unable to decrypt core."); + goto failed; + } + } else { + if (EVP_DecryptFinal_ex(&ctx, buf, &olen) == 0) { + pjdlog_error("Unable to decrypt core."); + goto failed; + } + } + + if (olen == 0) + continue; + + if (write(ofd, buf, olen) != olen) { + pjdlog_errno(LOG_ERR, "Unable to write data to %s", + output); + goto failed; + } + } while (bytes > 0); + + explicit_bzero(buf, sizeof(buf)); + EVP_CIPHER_CTX_cleanup(&ctx); + exit(0); +failed: + explicit_bzero(key, sizeof(key)); + explicit_bzero(buf, sizeof(buf)); + RSA_free(privkey); + EVP_CIPHER_CTX_cleanup(&ctx); + exit(1); +} + +int +main(int argc, char **argv) +{ + char core[PATH_MAX], encryptedcore[PATH_MAX], keyfile[PATH_MAX]; + struct stat sb; + const char *crashdir, *dumpnr, *privatekey; + int ch, debug; + size_t ii; + bool usesyslog; + + pjdlog_init(PJDLOG_MODE_STD); + pjdlog_prefix_set("(decryptcore) "); + + debug = 0; + *core = '\0'; + crashdir = NULL; + dumpnr = NULL; + *encryptedcore = '\0'; + *keyfile = '\0'; + privatekey = NULL; + usesyslog = false; + while ((ch = getopt(argc, argv, "Lc:d:e:k:n:p:v")) != -1) { + switch (ch) { + case 'L': + usesyslog = true; + break; + case 'c': + strncpy(core, optarg, sizeof(core)); + break; + case 'd': + crashdir = optarg; + break; + case 'e': + strncpy(encryptedcore, optarg, sizeof(encryptedcore)); + break; + case 'k': + strncpy(keyfile, optarg, sizeof(keyfile)); + break; + case 'n': + dumpnr = optarg; + break; + case 'p': + privatekey = optarg; + break; + case 'v': + debug++; + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (argc != 0) + usage(); + + /* Verify mutually exclusive options. */ + if ((crashdir != NULL || dumpnr != NULL) && + (*keyfile != '\0' || *encryptedcore != '\0' || *core != '\0')) { + usage(); + } + + /* + * Set key, encryptedcore and core file names using crashdir and dumpnr. + */ + if (dumpnr != NULL) { + for (ii = 0; ii < strnlen(dumpnr, PATH_MAX); ii++) { + if (isdigit((int)dumpnr[ii]) == 0) + usage(); + } + + if (crashdir == NULL) + crashdir = DECRYPTCORE_CRASHDIR; + PJDLOG_VERIFY(snprintf(keyfile, sizeof(keyfile), + "%s/key.%s", crashdir, dumpnr) > 0); + PJDLOG_VERIFY(snprintf(core, sizeof(core), + "%s/vmcore.%s", crashdir, dumpnr) > 0); + PJDLOG_VERIFY(snprintf(encryptedcore, sizeof(encryptedcore), + "%s/vmcore_encrypted.%s", crashdir, dumpnr) > 0); + } + + if (privatekey == NULL || *keyfile == '\0' || *encryptedcore == '\0' || + *core == '\0') { + usage(); + } + + if (usesyslog) + pjdlog_mode_set(PJDLOG_MODE_SYSLOG); + pjdlog_debug_set(debug); + + if (!decrypt(privatekey, keyfile, encryptedcore, core)) { + if (stat(core, &sb) == 0 && unlink(core) != 0) + pjdlog_exit(1, "Unable to remove core"); + exit(1); + } + + pjdlog_fini(); + + exit(0); +} diff --git a/sbin/dumpon/Makefile b/sbin/dumpon/Makefile index 782117c16ff0..d0700ab16a89 100644 --- a/sbin/dumpon/Makefile +++ b/sbin/dumpon/Makefile @@ -1,7 +1,15 @@ # $FreeBSD$ +.include + PACKAGE=runtime PROG= dumpon + +.if ${MK_OPENSSL} != "no" +LIBADD= crypto +CFLAGS+=-DHAVE_CRYPTO +.endif + MAN= dumpon.8 .include diff --git a/sbin/dumpon/dumpon.8 b/sbin/dumpon/dumpon.8 index 81377124d803..14b0665ec1bb 100644 --- a/sbin/dumpon/dumpon.8 +++ b/sbin/dumpon/dumpon.8 @@ -28,7 +28,7 @@ .\" From: @(#)swapon.8 8.1 (Berkeley) 6/5/93 .\" $FreeBSD$ .\" -.Dd October 3, 2016 +.Dd December 10, 2016 .Dt DUMPON 8 .Os .Sh NAME @@ -37,6 +37,7 @@ .Sh SYNOPSIS .Nm .Op Fl v +.Op Fl k Ar public_key_file .Ar special_file .Nm .Op Fl v @@ -56,7 +57,9 @@ normally occur from the system multi-user initialization file .Pa /etc/rc , controlled by the .Dq dumpdev -variable in the boot time configuration file +and +.Dq dumppubkey +variables in the boot time configuration file .Pa /etc/rc.conf . .Pp The default type of kernel crash dump is the mini crash dump. @@ -82,6 +85,35 @@ total amount of physical memory as reported by the variable. .Pp The +.Op Fl k Ar public_key_file +flag causes +.Nm +to generate a one-time key for kernel crash dump encryption. +The key will be replaced by a new one when the +.Nm +utility is run again. +The key is encrypted using +.Ar public_key_file . +This process is sandboxed using +.Xr capsicum 4 . +Both plain and encrypted keys are sent to the kernel using +.Dv DIOCSKERNELDUMP +.Xr ioctl 2 . +A user can specify the +.Ar public_key_file +in the +.Dq dumppubkey +variable defined in +.Pa /etc/rc.conf +for use with the +.Pa /etc/rc.d/dumpon +.Xr rc 8 +script. +This flag requires a kernel compiled with the +.Dv EKCD +kernel option. +.Pp +The .Fl l flag causes .Nm @@ -140,13 +172,95 @@ standard swap areas .It Pa /etc/rc.conf boot-time system configuration .El +.Sh EXAMPLES +In order to generate an RSA private key a user can use the +.Xr genrsa 1 +tool: +.Pp +.Dl # openssl genrsa -out private.pem 4096 +.Pp +A public key can be extracted from the private key using the +.Xr rsa 1 +tool: +.Pp +.Dl # openssl rsa -in private.pem -out public.pem -pubout +.Pp +Once the RSA keys are created the private key should be moved to a safe place. +Now +.Pa public.pem +can be used by +.Nm +to configure encrypted kernel crash dumps: +.Pp +.Dl # dumpon -k public.pem /dev/ada0s1b +.Pp +It is recommended to test if the kernel saves encrypted crash dumps using the +current configuration. +The easiest way to do that is to cause a kernel panic using the +.Xr ddb 4 +debugger: +.Pp +.Dl # sysctl debug.kdb.panic=1 +.Pp +In the debugger the following commands should be typed to write a core dump and +reboot: +.Pp +.Dl db> call doadump(0) +.Dl db> reset +.Pp +After reboot +.Xr savecore 8 +should be able to save the core dump in the core directory which is +.Pa /var/crash +by default: +.Pp +.Dl # savecore /var/crash /dev/ada0s1b +.Pp +Three files should be created in the core directory: +.Pa info.# , +.Pa key.# +and +.Pa vmcore_encrypted.# +where +.Dq # +is the number of the last core dump saved by +.Xr savecore 8 . +The +.Pa vmcore_encrypted.# +can be decrypted using the +.Xr decryptcore 8 +utility: +.Pp +.Dl # decryptcore -p private.pem -k key.# -e vmcore_encrypted.# -c vmcore.# +.Pp +or shorter: +.Pp +.Dl # decryptcore -p private.pem -n # +.Pp +The +.Pa vmcore.# +can be now examined using +.Xr kgdb 1 : +.Pp +.Dl # kgdb /usr/obj/sys/GENERIC/kernel.debug vmcore.# +.Pp +or shorter: +.Pp +.Dl # kgdb -n # /usr/obj/sys/GENERIC/kernel.debug +.Pp +The core was decrypted properly if +.Xr kgdb 1 +does not print any errors. .Sh SEE ALSO +.Xr kgdb 1 , +.Xr ddb 4 , .Xr fstab 5 , .Xr rc.conf 5 , .Xr config 8 , .Xr init 8 , .Xr loader 8 , .Xr rc 8 , +.Xr decryptcore 8 , .Xr savecore 8 , .Xr swapon 8 , .Xr panic 9 diff --git a/sbin/dumpon/dumpon.c b/sbin/dumpon/dumpon.c index 8407111c7f65..a1d77c852a48 100644 --- a/sbin/dumpon/dumpon.c +++ b/sbin/dumpon/dumpon.c @@ -42,13 +42,16 @@ static char sccsid[] = "From: @(#)swapon.c 8.1 (Berkeley) 6/5/93"; __FBSDID("$FreeBSD$"); #include +#include #include #include +#include #include #include #include #include +#include #include #include #include @@ -56,13 +59,19 @@ __FBSDID("$FreeBSD$"); #include #include +#ifdef HAVE_CRYPTO +#include +#include +#include +#endif + static int verbose; static void usage(void) { fprintf(stderr, "%s\n%s\n%s\n", - "usage: dumpon [-v] special_file", + "usage: dumpon [-v] [-k public_key_file] special_file", " dumpon [-v] off", " dumpon [-v] -l"); exit(EX_USAGE); @@ -94,6 +103,59 @@ check_size(int fd, const char *fn) } } +#ifdef HAVE_CRYPTO +static void +genkey(const char *pubkeyfile, struct diocskerneldump_arg *kda) +{ + FILE *fp; + RSA *pubkey; + + assert(pubkeyfile != NULL); + assert(kda != NULL); + + fp = NULL; + pubkey = NULL; + + fp = fopen(pubkeyfile, "r"); + if (fp == NULL) + err(1, "Unable to open %s", pubkeyfile); + + if (cap_enter() < 0 && errno != ENOSYS) + err(1, "Unable to enter capability mode"); + + pubkey = RSA_new(); + if (pubkey == NULL) { + errx(1, "Unable to allocate an RSA structure: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + + pubkey = PEM_read_RSA_PUBKEY(fp, &pubkey, NULL, NULL); + fclose(fp); + fp = NULL; + if (pubkey == NULL) + errx(1, "Unable to read data from %s.", pubkeyfile); + + kda->kda_encryptedkeysize = RSA_size(pubkey); + if (kda->kda_encryptedkeysize > KERNELDUMP_ENCKEY_MAX_SIZE) { + errx(1, "Public key has to be at most %db long.", + 8 * KERNELDUMP_ENCKEY_MAX_SIZE); + } + + kda->kda_encryptedkey = calloc(1, kda->kda_encryptedkeysize); + if (kda->kda_encryptedkey == NULL) + err(1, "Unable to allocate encrypted key"); + + kda->kda_encryption = KERNELDUMP_ENC_AES_256_CBC; + arc4random_buf(kda->kda_key, sizeof(kda->kda_key)); + if (RSA_public_encrypt(sizeof(kda->kda_key), kda->kda_key, + kda->kda_encryptedkey, pubkey, + RSA_PKCS1_PADDING) != (int)kda->kda_encryptedkeysize) { + errx(1, "Unable to encrypt the one-time key."); + } + RSA_free(pubkey); +} +#endif + static void listdumpdev(void) { @@ -123,13 +185,20 @@ listdumpdev(void) int main(int argc, char *argv[]) { + struct diocskerneldump_arg kda; + const char *pubkeyfile; int ch; int i, fd; - u_int u; int do_listdumpdev = 0; + bool enable; - while ((ch = getopt(argc, argv, "lv")) != -1) + pubkeyfile = NULL; + + while ((ch = getopt(argc, argv, "k:lv")) != -1) switch((char)ch) { + case 'k': + pubkeyfile = optarg; + break; case 'l': do_listdumpdev = 1; break; @@ -151,7 +220,15 @@ main(int argc, char *argv[]) if (argc != 1) usage(); - if (strcmp(argv[0], "off") != 0) { + enable = (strcmp(argv[0], "off") != 0); +#ifndef HAVE_CRYPTO + if (pubkeyfile != NULL) { + enable = false; + warnx("Unable to use the public key. Recompile dumpon with OpenSSL support."); + } +#endif + + if (enable) { char tmp[PATH_MAX]; char *dumpdev; @@ -171,18 +248,32 @@ main(int argc, char *argv[]) if (fd < 0) err(EX_OSFILE, "%s", dumpdev); check_size(fd, dumpdev); - u = 0; - i = ioctl(fd, DIOCSKERNELDUMP, &u); - u = 1; - i = ioctl(fd, DIOCSKERNELDUMP, &u); + bzero(&kda, sizeof(kda)); + + kda.kda_enable = 0; + i = ioctl(fd, DIOCSKERNELDUMP, &kda); + explicit_bzero(&kda, sizeof(kda)); + +#ifdef HAVE_CRYPTO + if (pubkeyfile != NULL) + genkey(pubkeyfile, &kda); +#endif + + kda.kda_enable = 1; + i = ioctl(fd, DIOCSKERNELDUMP, &kda); + explicit_bzero(kda.kda_encryptedkey, kda.kda_encryptedkeysize); + free(kda.kda_encryptedkey); + explicit_bzero(&kda, sizeof(kda)); if (i == 0 && verbose) printf("kernel dumps on %s\n", dumpdev); } else { fd = open(_PATH_DEVNULL, O_RDONLY); if (fd < 0) err(EX_OSFILE, "%s", _PATH_DEVNULL); - u = 0; - i = ioctl(fd, DIOCSKERNELDUMP, &u); + + kda.kda_enable = 0; + i = ioctl(fd, DIOCSKERNELDUMP, &kda); + explicit_bzero(&kda, sizeof(kda)); if (i == 0 && verbose) printf("kernel dumps disabled\n"); } diff --git a/sbin/savecore/savecore.8 b/sbin/savecore/savecore.8 index af46d05e4d62..7d54c823affb 100644 --- a/sbin/savecore/savecore.8 +++ b/sbin/savecore/savecore.8 @@ -28,7 +28,7 @@ .\" From: @(#)savecore.8 8.1 (Berkeley) 6/5/93 .\" $FreeBSD$ .\" -.Dd December 1, 2015 +.Dd December 10, 2016 .Dt SAVECORE 8 .Os .Sh NAME @@ -119,6 +119,10 @@ If it passes these checks, it saves the core image in .Ar directory Ns Pa /vmcore.# and information about the core in .Ar directory Ns Pa /info.# . +If the core is encrypted, it saves the dump key in +.Ar directory Ns Pa /key.# . +The core can be later decrypted using +.Xr decryptcore 8 . For kernel textdumps generated with the .Xr textdump 4 facility, output will be stored in the @@ -166,6 +170,7 @@ is meant to be called near the end of the initialization file .Xr xo_parse_args 3 , .Xr textdump 4 , .Xr tar 5 , +.Xr decryptcore 8 , .Xr dumpon 8 , .Xr syslogd 8 .Sh HISTORY diff --git a/sbin/savecore/savecore.c b/sbin/savecore/savecore.c index 94b800909174..b07e7e1e018c 100644 --- a/sbin/savecore/savecore.c +++ b/sbin/savecore/savecore.c @@ -74,6 +74,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include #include @@ -183,6 +184,28 @@ writebounds(int bounds) { fclose(fp); } +static bool +writekey(const char *keyname, uint8_t *dumpkey, uint32_t dumpkeysize) +{ + int fd; + + fd = open(keyname, O_WRONLY | O_CREAT | O_TRUNC, 0600); + if (fd == -1) { + syslog(LOG_ERR, "Unable to open %s to write the key: %m.", + keyname); + return (false); + } + + if (write(fd, dumpkey, dumpkeysize) != (ssize_t)dumpkeysize) { + syslog(LOG_ERR, "Unable to write the key to %s: %m.", keyname); + close(fd); + return (false); + } + + close(fd); + return (true); +} + static off_t file_size(const char *path) { @@ -238,8 +261,11 @@ symlinks_remove(void) { (void)unlink("info.last"); + (void)unlink("key.last"); (void)unlink("vmcore.last"); (void)unlink("vmcore.last.gz"); + (void)unlink("vmcore_encrypted.last"); + (void)unlink("vmcore_encrypted.last.gz"); (void)unlink("textdump.tar.last"); (void)unlink("textdump.tar.last.gz"); } @@ -292,8 +318,8 @@ check_space(const char *savedir, off_t dumpsize, int bounds) #define BLOCKMASK (~(BLOCKSIZE-1)) static int -DoRegularFile(int fd, off_t dumpsize, char *buf, const char *device, - const char *filename, FILE *fp) +DoRegularFile(int fd, bool isencrypted, off_t dumpsize, char *buf, + const char *device, const char *filename, FILE *fp) { int he, hs, nr, nw, wl; off_t dmpcnt, origsize; @@ -315,7 +341,7 @@ DoRegularFile(int fd, off_t dumpsize, char *buf, const char *device, nerr++; return (-1); } - if (compress) { + if (compress || isencrypted) { nw = fwrite(buf, 1, wl, fp); } else { for (nw = 0; nw < nr; nw = he) { @@ -436,9 +462,11 @@ DoFile(const char *savedir, const char *device) { xo_handle_t *xostdout, *xoinfo; static char infoname[PATH_MAX], corename[PATH_MAX], linkname[PATH_MAX]; + static char keyname[PATH_MAX]; static char *buf = NULL; char *temp = NULL; struct kerneldumpheader kdhf, kdhl; + uint8_t *dumpkey; off_t mediasize, dumpsize, firsthd, lasthd; FILE *info, *fp; mode_t oumask; @@ -446,6 +474,8 @@ DoFile(const char *savedir, const char *device) int bounds, status; u_int sectorsize, xostyle; int istextdump; + uint32_t dumpkeysize; + bool isencrypted, ret; bounds = getbounds(); mediasize = 0; @@ -581,7 +611,8 @@ DoFile(const char *savedir, const char *device) goto closefd; } dumpsize = dtoh64(kdhl.dumplength); - firsthd = lasthd - dumpsize - sectorsize; + dumpkeysize = dtoh32(kdhl.dumpkeysize); + firsthd = lasthd - dumpsize - sectorsize - dumpkeysize; if (lseek(fd, firsthd, SEEK_SET) != firsthd || read(fd, temp, sectorsize) != (ssize_t)sectorsize) { syslog(LOG_ERR, @@ -649,13 +680,16 @@ DoFile(const char *savedir, const char *device) } oumask = umask(S_IRWXG|S_IRWXO); /* Restrict access to the core file.*/ + isencrypted = (dumpkeysize > 0); if (compress) { snprintf(corename, sizeof(corename), "%s.%d.gz", - istextdump ? "textdump.tar" : "vmcore", bounds); + istextdump ? "textdump.tar" : + (isencrypted ? "vmcore_encrypted" : "vmcore"), bounds); fp = zopen(corename, "w"); } else { snprintf(corename, sizeof(corename), "%s.%d", - istextdump ? "textdump.tar" : "vmcore", bounds); + istextdump ? "textdump.tar" : + (isencrypted ? "vmcore_encrypted" : "vmcore"), bounds); fp = fopen(corename, "w"); } if (fp == NULL) { @@ -692,17 +726,42 @@ DoFile(const char *savedir, const char *device) xo_finish_h(xoinfo); fclose(info); - syslog(LOG_NOTICE, "writing %score to %s/%s", - compress ? "compressed " : "", savedir, corename); + if (isencrypted) { + dumpkey = calloc(1, dumpkeysize); + if (dumpkey == NULL) { + syslog(LOG_ERR, "Unable to allocate kernel dump key."); + nerr++; + goto closeall; + } + + if (read(fd, dumpkey, dumpkeysize) != (ssize_t)dumpkeysize) { + syslog(LOG_ERR, "Unable to read kernel dump key: %m."); + nerr++; + goto closeall; + } + + snprintf(keyname, sizeof(keyname), "key.%d", bounds); + ret = writekey(keyname, dumpkey, dumpkeysize); + explicit_bzero(dumpkey, dumpkeysize); + if (!ret) { + nerr++; + goto closeall; + } + } + + syslog(LOG_NOTICE, "writing %s%score to %s/%s", + isencrypted ? "encrypted " : "", compress ? "compressed " : "", + savedir, corename); if (istextdump) { if (DoTextdumpFile(fd, dumpsize, lasthd, buf, device, corename, fp) < 0) goto closeall; } else { - if (DoRegularFile(fd, dumpsize, buf, device, corename, fp) - < 0) + if (DoRegularFile(fd, isencrypted, dumpsize, buf, device, + corename, fp) < 0) { goto closeall; + } } if (verbose) printf("\n"); @@ -718,12 +777,21 @@ DoFile(const char *savedir, const char *device) syslog(LOG_WARNING, "unable to create symlink %s/%s: %m", savedir, "info.last"); } + if (isencrypted) { + if (symlink(keyname, "key.last") == -1) { + syslog(LOG_WARNING, + "unable to create symlink %s/%s: %m", savedir, + "key.last"); + } + } if (compress) { snprintf(linkname, sizeof(linkname), "%s.last.gz", - istextdump ? "textdump.tar" : "vmcore"); + istextdump ? "textdump.tar" : + (isencrypted ? "vmcore_encrypted" : "vmcore")); } else { snprintf(linkname, sizeof(linkname), "%s.last", - istextdump ? "textdump.tar" : "vmcore"); + istextdump ? "textdump.tar" : + (isencrypted ? "vmcore_encrypted" : "vmcore")); } if (symlink(corename, linkname) == -1) { syslog(LOG_WARNING, "unable to create symlink %s/%s: %m", diff --git a/share/man/man5/rc.conf.5 b/share/man/man5/rc.conf.5 index ed04a77b7a04..46b6aa2063ef 100644 --- a/share/man/man5/rc.conf.5 +++ b/share/man/man5/rc.conf.5 @@ -24,7 +24,7 @@ .\" .\" $FreeBSD$ .\" -.Dd July 13, 2016 +.Dd December 10, 2016 .Dt RC.CONF 5 .Os .Sh NAME @@ -3502,6 +3502,18 @@ to not run at boot time when .Va dumpdir 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 .Pq Vt bool If set to diff --git a/sys/amd64/amd64/minidump_machdep.c b/sys/amd64/amd64/minidump_machdep.c index 11bcf5f863ca..a758c7b24d6a 100644 --- a/sys/amd64/amd64/minidump_machdep.c +++ b/sys/amd64/amd64/minidump_machdep.c @@ -223,7 +223,6 @@ minidumpsys(struct dumperinfo *di) int error; uint64_t bits; uint64_t *pml4, *pdp, *pd, *pt, pa; - size_t size; int i, ii, j, k, n, bit; int retry_count; struct minidumphdr mdhdr; @@ -321,14 +320,21 @@ minidumpsys(struct dumperinfo *di) dumpsize += PAGE_SIZE; /* Determine dump offset on device. */ - if (di->mediasize < SIZEOF_METADATA + dumpsize + di->blocksize * 2) { + if (di->mediasize < SIZEOF_METADATA + dumpsize + di->blocksize * 2 + + kerneldumpcrypto_dumpkeysize(di->kdc)) { error = E2BIG; goto fail; } dumplo = di->mediaoffset + di->mediasize - dumpsize; dumplo -= di->blocksize * 2; + dumplo -= kerneldumpcrypto_dumpkeysize(di->kdc); progress = dumpsize; + /* Initialize kernel dump crypto. */ + error = kerneldumpcrypto_init(di->kdc); + if (error) + goto fail; + /* Initialize mdhdr */ bzero(&mdhdr, sizeof(mdhdr)); strcpy(mdhdr.magic, MINIDUMP_MAGIC); @@ -340,16 +346,23 @@ minidumpsys(struct dumperinfo *di) mdhdr.dmapbase = DMAP_MIN_ADDRESS; mdhdr.dmapend = DMAP_MAX_ADDRESS; - mkdumpheader(&kdh, KERNELDUMPMAGIC, KERNELDUMP_AMD64_VERSION, dumpsize, di->blocksize); + mkdumpheader(&kdh, KERNELDUMPMAGIC, KERNELDUMP_AMD64_VERSION, dumpsize, + kerneldumpcrypto_dumpkeysize(di->kdc), di->blocksize); printf("Dumping %llu out of %ju MB:", (long long)dumpsize >> 20, ptoa((uintmax_t)physmem) / 1048576); /* Dump leader */ - error = dump_write_pad(di, &kdh, 0, dumplo, sizeof(kdh), &size); + error = dump_write_header(di, &kdh, 0, dumplo); if (error) goto fail; - dumplo += size; + dumplo += di->blocksize; + + /* Dump key */ + error = dump_write_key(di, 0, dumplo); + if (error) + goto fail; + dumplo += kerneldumpcrypto_dumpkeysize(di->kdc); /* Dump my header */ bzero(&fakepd, sizeof(fakepd)); @@ -434,10 +447,10 @@ minidumpsys(struct dumperinfo *di) goto fail; /* Dump trailer */ - error = dump_write_pad(di, &kdh, 0, dumplo, sizeof(kdh), &size); + error = dump_write_header(di, &kdh, 0, dumplo); if (error) goto fail; - dumplo += size; + dumplo += di->blocksize; /* Signal completion, signoff and exit stage left. */ dump_write(di, NULL, 0, 0, 0); diff --git a/sys/arm/arm/minidump_machdep.c b/sys/arm/arm/minidump_machdep.c index e7340dcf33db..c5a139f74702 100644 --- a/sys/arm/arm/minidump_machdep.c +++ b/sys/arm/arm/minidump_machdep.c @@ -238,15 +238,22 @@ minidumpsys(struct dumperinfo *di) dumpsize += PAGE_SIZE; /* Determine dump offset on device. */ - if (di->mediasize < SIZEOF_METADATA + dumpsize + sizeof(kdh) * 2) { + if (di->mediasize < SIZEOF_METADATA + dumpsize + di->blocksize * 2 + + kerneldumpcrypto_dumpkeysize(di->kdc)) { error = ENOSPC; goto fail; } dumplo = di->mediaoffset + di->mediasize - dumpsize; - dumplo -= sizeof(kdh) * 2; + dumplo -= di->blocksize * 2; + dumplo -= kerneldumpcrypto_dumpkeysize(di->kdc); progress = dumpsize; + /* Initialize kernel dump crypto. */ + error = kerneldumpcrypto_init(di->kdc); + if (error) + goto fail; + /* Initialize mdhdr */ bzero(&mdhdr, sizeof(mdhdr)); strcpy(mdhdr.magic, MINIDUMP_MAGIC); @@ -262,16 +269,22 @@ minidumpsys(struct dumperinfo *di) mdhdr.mmuformat = MINIDUMP_MMU_FORMAT_V4; #endif mkdumpheader(&kdh, KERNELDUMPMAGIC, KERNELDUMP_ARM_VERSION, dumpsize, - di->blocksize); + kerneldumpcrypto_dumpkeysize(di->kdc), di->blocksize); printf("Physical memory: %u MB\n", ptoa((uintmax_t)physmem) / 1048576); printf("Dumping %llu MB:", (long long)dumpsize >> 20); /* Dump leader */ - error = dump_write(di, &kdh, 0, dumplo, sizeof(kdh)); + error = dump_write_header(di, &kdh, 0, dumplo); if (error) goto fail; - dumplo += sizeof(kdh); + dumplo += di->blocksize; + + /* Dump key */ + error = dump_write_key(di, 0, dumplo); + if (error) + goto fail; + dumplo += kerneldumpcrypto_dumpkeysize(di->kdc); /* Dump my header */ bzero(dumpbuf, sizeof(dumpbuf)); @@ -348,10 +361,10 @@ minidumpsys(struct dumperinfo *di) goto fail; /* Dump trailer */ - error = dump_write(di, &kdh, 0, dumplo, sizeof(kdh)); + error = dump_write_header(di, &kdh, 0, dumplo); if (error) goto fail; - dumplo += sizeof(kdh); + dumplo += di->blocksize; /* Signal completion, signoff and exit stage left. */ dump_write(di, NULL, 0, 0, 0); diff --git a/sys/arm64/arm64/minidump_machdep.c b/sys/arm64/arm64/minidump_machdep.c index 27c2081ef78d..2aeb07d07366 100644 --- a/sys/arm64/arm64/minidump_machdep.c +++ b/sys/arm64/arm64/minidump_machdep.c @@ -281,14 +281,21 @@ minidumpsys(struct dumperinfo *di) dumpsize += PAGE_SIZE; /* Determine dump offset on device. */ - if (di->mediasize < SIZEOF_METADATA + dumpsize + sizeof(kdh) * 2) { + if (di->mediasize < SIZEOF_METADATA + dumpsize + di->blocksize * 2 + + kerneldumpcrypto_dumpkeysize(di->kdc)) { error = E2BIG; goto fail; } dumplo = di->mediaoffset + di->mediasize - dumpsize; - dumplo -= sizeof(kdh) * 2; + dumplo -= di->blocksize * 2; + dumplo -= kerneldumpcrypto_dumpkeysize(di->kdc); progress = dumpsize; + /* Initialize kernel dump crypto. */ + error = kerneldumpcrypto_init(di->kdc); + if (error) + goto fail; + /* Initialize mdhdr */ bzero(&mdhdr, sizeof(mdhdr)); strcpy(mdhdr.magic, MINIDUMP_MAGIC); @@ -302,16 +309,22 @@ minidumpsys(struct dumperinfo *di) mdhdr.dmapend = DMAP_MAX_ADDRESS; mkdumpheader(&kdh, KERNELDUMPMAGIC, KERNELDUMP_AARCH64_VERSION, - dumpsize, di->blocksize); + dumpsize, kerneldumpcrypto_dumpkeysize(di->kdc), di->blocksize); printf("Dumping %llu out of %ju MB:", (long long)dumpsize >> 20, ptoa((uintmax_t)physmem) / 1048576); /* Dump leader */ - error = dump_write(di, &kdh, 0, dumplo, sizeof(kdh)); + error = dump_write_header(di, &kdh, 0, dumplo); if (error) goto fail; - dumplo += sizeof(kdh); + dumplo += di->blocksize; + + /* Dump key */ + error = dump_write_key(di, 0, dumplo); + if (error) + goto fail; + dumplo += kerneldumpcrypto_dumpkeysize(di->kdc); /* Dump my header */ bzero(&tmpbuffer, sizeof(tmpbuffer)); @@ -410,10 +423,10 @@ minidumpsys(struct dumperinfo *di) goto fail; /* Dump trailer */ - error = dump_write(di, &kdh, 0, dumplo, sizeof(kdh)); + error = dump_write_header(di, &kdh, 0, dumplo); if (error) goto fail; - dumplo += sizeof(kdh); + dumplo += di->blocksize; /* Signal completion, signoff and exit stage left. */ dump_write(di, NULL, 0, 0, 0); diff --git a/sys/conf/NOTES b/sys/conf/NOTES index 061438615570..cbd0934a032b 100644 --- a/sys/conf/NOTES +++ b/sys/conf/NOTES @@ -3072,3 +3072,6 @@ options EVDEV_SUPPORT # evdev support in legacy drivers options EVDEV_DEBUG # enable event debug msgs device uinput # install /dev/uinput cdev options UINPUT_DEBUG # enable uinput debug msgs + +# Encrypted kernel crash dumps. +options EKCD diff --git a/sys/conf/files b/sys/conf/files index 9b4042726f68..23e9c6f32a56 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -594,13 +594,13 @@ crypto/camellia/camellia-api.c optional crypto | ipsec crypto/des/des_ecb.c optional crypto | ipsec | netsmb crypto/des/des_setkey.c optional crypto | ipsec | netsmb crypto/rc4/rc4.c optional netgraph_mppc_encryption | kgssapi -crypto/rijndael/rijndael-alg-fst.c optional crypto | geom_bde | \ +crypto/rijndael/rijndael-alg-fst.c optional crypto | ekcd | geom_bde | \ ipsec | random !random_loadable | wlan_ccmp -crypto/rijndael/rijndael-api-fst.c optional geom_bde | random !random_loadable +crypto/rijndael/rijndael-api-fst.c optional ekcd | geom_bde | random !random_loadable crypto/rijndael/rijndael-api.c optional crypto | ipsec | wlan_ccmp crypto/sha1.c optional carp | crypto | ipsec | \ netgraph_mppc_encryption | sctp -crypto/sha2/sha256c.c optional crypto | geom_bde | ipsec | random !random_loadable | \ +crypto/sha2/sha256c.c optional crypto | ekcd | geom_bde | ipsec | random !random_loadable | \ sctp | zfs crypto/sha2/sha512c.c optional crypto | geom_bde | ipsec | zfs crypto/skein/skein.c optional crypto | zfs diff --git a/sys/conf/options b/sys/conf/options index 07d76390505f..1f20983c2097 100644 --- a/sys/conf/options +++ b/sys/conf/options @@ -1004,3 +1004,6 @@ UINPUT_DEBUG opt_evdev.h # Hyper-V network driver HN_DEBUG opt_hn.h + +# Encrypted kernel crash dumps +EKCD opt_ekcd.h diff --git a/sys/ddb/db_textdump.c b/sys/ddb/db_textdump.c index aeeeec863cec..66d8d2c84032 100644 --- a/sys/ddb/db_textdump.c +++ b/sys/ddb/db_textdump.c @@ -427,6 +427,7 @@ textdump_dump_version(struct dumperinfo *di) void textdump_dumpsys(struct dumperinfo *di) { + struct kerneldumpcrypto *kdc; off_t dumplen, trailer_offset; if (di->blocksize != TEXTDUMP_BLOCKSIZE) { @@ -448,6 +449,12 @@ textdump_dumpsys(struct dumperinfo *di) } textdump_error = 0; + /* + * Disable EKCD because we don't provide encrypted textdumps. + */ + kdc = di->kdc; + di->kdc = NULL; + /* * Position the start of the dump so that we'll write the kernel dump * trailer immediately before the end of the partition, and then work @@ -456,7 +463,8 @@ textdump_dumpsys(struct dumperinfo *di) */ textdump_offset = di->mediasize - sizeof(kdh); textdump_saveoff(&trailer_offset); - mkdumpheader(&kdh, TEXTDUMPMAGIC, KERNELDUMP_TEXT_VERSION, 0, TEXTDUMP_BLOCKSIZE); + mkdumpheader(&kdh, TEXTDUMPMAGIC, KERNELDUMP_TEXT_VERSION, 0, 0, + TEXTDUMP_BLOCKSIZE); (void)textdump_writenextblock(di, (char *)&kdh); /* @@ -481,7 +489,7 @@ textdump_dumpsys(struct dumperinfo *di) * size. */ dumplen = trailer_offset - (textdump_offset + TEXTDUMP_BLOCKSIZE); - mkdumpheader(&kdh, TEXTDUMPMAGIC, KERNELDUMP_TEXT_VERSION, dumplen, + mkdumpheader(&kdh, TEXTDUMPMAGIC, KERNELDUMP_TEXT_VERSION, dumplen, 0, TEXTDUMP_BLOCKSIZE); (void)textdump_writenextblock(di, (char *)&kdh); textdump_restoreoff(trailer_offset); @@ -499,6 +507,11 @@ textdump_dumpsys(struct dumperinfo *di) else printf("Textdump complete.\n"); textdump_pending = 0; + + /* + * Restore EKCD status. + */ + di->kdc = kdc; } /*- diff --git a/sys/dev/null/null.c b/sys/dev/null/null.c index c8966df8ac4b..d946da6208ff 100644 --- a/sys/dev/null/null.c +++ b/sys/dev/null/null.c @@ -30,6 +30,8 @@ #include __FBSDID("$FreeBSD$"); +#include "opt_compat.h" + #include #include #include @@ -108,8 +110,11 @@ null_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t data __unused, error = 0; switch (cmd) { +#ifdef COMPAT_FREEBSD11 + case DIOCSKERNELDUMP_FREEBSD11: +#endif case DIOCSKERNELDUMP: - error = set_dumper(NULL, NULL, td); + error = set_dumper(NULL, NULL, td, 0, NULL, 0, NULL); break; case FIONBIO: break; diff --git a/sys/geom/geom_dev.c b/sys/geom/geom_dev.c index 3ccbd583fa2d..82f7d4bdb9c3 100644 --- a/sys/geom/geom_dev.c +++ b/sys/geom/geom_dev.c @@ -36,6 +36,8 @@ #include __FBSDID("$FreeBSD$"); +#include "opt_compat.h" + #include #include #include @@ -128,36 +130,44 @@ g_dev_fini(struct g_class *mp) } static int -g_dev_setdumpdev(struct cdev *dev, struct thread *td) +g_dev_setdumpdev(struct cdev *dev, struct diocskerneldump_arg *kda, + struct thread *td) { struct g_kerneldump kd; struct g_consumer *cp; int error, len; - if (dev == NULL) - return (set_dumper(NULL, NULL, td)); + if (dev == NULL || kda == NULL) + return (set_dumper(NULL, NULL, td, 0, NULL, 0, NULL)); cp = dev->si_drv2; len = sizeof(kd); kd.offset = 0; kd.length = OFF_MAX; error = g_io_getattr("GEOM::kerneldump", cp, &len, &kd); - if (error == 0) { - error = set_dumper(&kd.di, devtoname(dev), td); - if (error == 0) - dev->si_flags |= SI_DUMPDEV; - } + 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); + if (error == 0) + dev->si_flags |= SI_DUMPDEV; + return (error); } static int init_dumpdev(struct cdev *dev) { + struct diocskerneldump_arg kda; struct g_consumer *cp; const char *devprefix = "/dev/", *devname; int error; size_t len; + bzero(&kda, sizeof(kda)); + kda.kda_enable = 1; + if (dumpdev == NULL) return (0); @@ -173,7 +183,7 @@ init_dumpdev(struct cdev *dev) if (error != 0) return (error); - error = g_dev_setdumpdev(dev, curthread); + error = g_dev_setdumpdev(dev, &kda, curthread); if (error == 0) { freeenv(dumpdev); dumpdev = NULL; @@ -493,12 +503,56 @@ g_dev_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread case DIOCGFRONTSTUFF: error = g_io_getattr("GEOM::frontstuff", cp, &i, data); break; - case DIOCSKERNELDUMP: - if (*(u_int *)data == 0) - error = g_dev_setdumpdev(NULL, td); +#ifdef COMPAT_FREEBSD11 + case DIOCSKERNELDUMP_FREEBSD11: + { + struct diocskerneldump_arg kda; + + 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); else - error = g_dev_setdumpdev(dev, td); + error = g_dev_setdumpdev(dev, &kda, td); break; + } +#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); + 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); + } else { + encryptedkey = NULL; + } + if (error == 0) { + kda->kda_encryptedkey = encryptedkey; + error = g_dev_setdumpdev(dev, kda, td); + } + if (encryptedkey != NULL) { + explicit_bzero(encryptedkey, kda->kda_encryptedkeysize); + free(encryptedkey, M_TEMP); + } + explicit_bzero(kda, sizeof(*kda)); + break; + } case DIOCGFLUSH: error = g_io_flush(cp); break; @@ -756,7 +810,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); + (void)set_dumper(NULL, NULL, curthread, 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/i386/i386/minidump_machdep.c b/sys/i386/i386/minidump_machdep.c index 6fd11f3721ac..b50a0de82a62 100644 --- a/sys/i386/i386/minidump_machdep.c +++ b/sys/i386/i386/minidump_machdep.c @@ -245,14 +245,21 @@ minidumpsys(struct dumperinfo *di) dumpsize += PAGE_SIZE; /* Determine dump offset on device. */ - if (di->mediasize < SIZEOF_METADATA + dumpsize + sizeof(kdh) * 2) { + if (di->mediasize < SIZEOF_METADATA + dumpsize + di->blocksize * 2 + + kerneldumpcrypto_dumpkeysize(di->kdc)) { error = ENOSPC; goto fail; } dumplo = di->mediaoffset + di->mediasize - dumpsize; - dumplo -= sizeof(kdh) * 2; + dumplo -= di->blocksize * 2; + dumplo -= kerneldumpcrypto_dumpkeysize(di->kdc); progress = dumpsize; + /* Initialize kernel dump crypto. */ + error = kerneldumpcrypto_init(di->kdc); + if (error) + goto fail; + /* Initialize mdhdr */ bzero(&mdhdr, sizeof(mdhdr)); strcpy(mdhdr.magic, MINIDUMP_MAGIC); @@ -265,16 +272,23 @@ minidumpsys(struct dumperinfo *di) mdhdr.paemode = 1; #endif - mkdumpheader(&kdh, KERNELDUMPMAGIC, KERNELDUMP_I386_VERSION, dumpsize, di->blocksize); + mkdumpheader(&kdh, KERNELDUMPMAGIC, KERNELDUMP_I386_VERSION, dumpsize, + kerneldumpcrypto_dumpkeysize(di->kdc), di->blocksize); printf("Physical memory: %ju MB\n", ptoa((uintmax_t)physmem) / 1048576); printf("Dumping %llu MB:", (long long)dumpsize >> 20); /* Dump leader */ - error = dump_write(di, &kdh, 0, dumplo, sizeof(kdh)); + error = dump_write_header(di, &kdh, 0, dumplo); if (error) goto fail; - dumplo += sizeof(kdh); + dumplo += di->blocksize; + + /* Dump key */ + error = dump_write_key(di, 0, dumplo); + if (error) + goto fail; + dumplo += kerneldumpcrypto_dumpkeysize(di->kdc); /* Dump my header */ bzero(&fakept, sizeof(fakept)); @@ -349,10 +363,10 @@ minidumpsys(struct dumperinfo *di) goto fail; /* Dump trailer */ - error = dump_write(di, &kdh, 0, dumplo, sizeof(kdh)); + error = dump_write_header(di, &kdh, 0, dumplo); if (error) goto fail; - dumplo += sizeof(kdh); + dumplo += di->blocksize; /* Signal completion, signoff and exit stage left. */ dump_write(di, NULL, 0, 0, 0); diff --git a/sys/kern/kern_dump.c b/sys/kern/kern_dump.c index 8e922a1e1bd7..986149782e75 100644 --- a/sys/kern/kern_dump.c +++ b/sys/kern/kern_dump.c @@ -116,6 +116,29 @@ dumpsys_gen_write_aux_headers(struct dumperinfo *di) } #endif +int +dumpsys_buf_seek(struct dumperinfo *di, size_t sz) +{ + static uint8_t buf[DEV_BSIZE]; + size_t nbytes; + int error; + + bzero(buf, sizeof(buf)); + + while (sz > 0) { + nbytes = MIN(sz, sizeof(buf)); + + error = dump_write(di, buf, 0, dumplo, nbytes); + if (error) + return (error); + dumplo += nbytes; + + sz -= nbytes; + } + + return (0); +} + int dumpsys_buf_write(struct dumperinfo *di, char *ptr, size_t sz) { @@ -284,7 +307,7 @@ dumpsys_generic(struct dumperinfo *di) Elf_Ehdr ehdr; uint64_t dumpsize; off_t hdrgap; - size_t hdrsz, size; + size_t hdrsz; int error; #ifndef __powerpc__ @@ -325,24 +348,37 @@ dumpsys_generic(struct dumperinfo *di) hdrgap = fileofs - roundup2((off_t)hdrsz, di->blocksize); /* Determine dump offset on device. */ - if (di->mediasize < SIZEOF_METADATA + dumpsize + di->blocksize * 2) { + if (di->mediasize < SIZEOF_METADATA + dumpsize + di->blocksize * 2 + + kerneldumpcrypto_dumpkeysize(di->kdc)) { error = ENOSPC; goto fail; } dumplo = di->mediaoffset + di->mediasize - dumpsize; dumplo -= di->blocksize * 2; + dumplo -= kerneldumpcrypto_dumpkeysize(di->kdc); + + /* Initialize kernel dump crypto. */ + error = kerneldumpcrypto_init(di->kdc); + if (error) + goto fail; mkdumpheader(&kdh, KERNELDUMPMAGIC, KERNELDUMP_ARCH_VERSION, dumpsize, - di->blocksize); + kerneldumpcrypto_dumpkeysize(di->kdc), di->blocksize); printf("Dumping %ju MB (%d chunks)\n", (uintmax_t)dumpsize >> 20, ehdr.e_phnum - DUMPSYS_NUM_AUX_HDRS); /* Dump leader */ - error = dump_write_pad(di, &kdh, 0, dumplo, sizeof(kdh), &size); + error = dump_write_header(di, &kdh, 0, dumplo); if (error) goto fail; - dumplo += size; + dumplo += di->blocksize; + + /* Dump key */ + error = dump_write_key(di, 0, dumplo); + if (error) + goto fail; + dumplo += kerneldumpcrypto_dumpkeysize(di->kdc); /* Dump ELF header */ error = dumpsys_buf_write(di, (char*)&ehdr, sizeof(ehdr)); @@ -365,7 +401,9 @@ dumpsys_generic(struct dumperinfo *di) * boundary. We cannot use MD_ALIGN on dumplo, because we don't * care and may very well be unaligned within the dump device. */ - dumplo += hdrgap; + error = dumpsys_buf_seek(di, (size_t)hdrgap); + if (error) + goto fail; /* Dump memory chunks (updates dumplo) */ error = dumpsys_foreach_chunk(dumpsys_cb_dumpdata, di); @@ -373,9 +411,10 @@ dumpsys_generic(struct dumperinfo *di) goto fail; /* Dump trailer */ - error = dump_write_pad(di, &kdh, 0, dumplo, sizeof(kdh), &size); + error = dump_write_header(di, &kdh, 0, dumplo); if (error) goto fail; + dumplo += di->blocksize; /* Signal completion, signoff and exit stage left. */ dump_write(di, NULL, 0, 0, 0); diff --git a/sys/kern/kern_shutdown.c b/sys/kern/kern_shutdown.c index 79c4c30d7cc9..f981c8093623 100644 --- a/sys/kern/kern_shutdown.c +++ b/sys/kern/kern_shutdown.c @@ -38,6 +38,7 @@ __FBSDID("$FreeBSD$"); #include "opt_ddb.h" +#include "opt_ekcd.h" #include "opt_kdb.h" #include "opt_panic.h" #include "opt_sched.h" @@ -71,6 +72,9 @@ __FBSDID("$FreeBSD$"); #include #include +#include +#include + #include #include @@ -143,6 +147,22 @@ int suspend_blocked = 0; SYSCTL_INT(_kern, OID_AUTO, suspend_blocked, CTLFLAG_RW, &suspend_blocked, 0, "Block suspend due to a pending shutdown"); +#ifdef EKCD +FEATURE(ekcd, "Encrypted kernel crash dumps support"); + +MALLOC_DEFINE(M_EKCD, "ekcd", "Encrypted kernel crash dumps data"); + +struct kerneldumpcrypto { + uint8_t kdc_encryption; + uint8_t kdc_iv[KERNELDUMP_IV_MAX_SIZE]; + keyInstance kdc_ki; + cipherInstance kdc_ci; + off_t kdc_nextoffset; + uint32_t kdc_dumpkeysize; + struct kerneldumpkey kdc_dumpkey[]; +}; +#endif + /* * Variable panicstr contains argument to first call to panic; used as flag * to indicate that the kernel has already called panic. @@ -838,9 +858,111 @@ static char dumpdevname[sizeof(((struct cdev*)NULL)->si_name)]; SYSCTL_STRING(_kern_shutdown, OID_AUTO, dumpdevname, CTLFLAG_RD, dumpdevname, 0, "Device for kernel dumps"); +#ifdef EKCD +static struct kerneldumpcrypto * +kerneldumpcrypto_create(size_t blocksize, uint8_t encryption, + const uint8_t *key, uint32_t encryptedkeysize, const uint8_t *encryptedkey) +{ + struct kerneldumpcrypto *kdc; + struct kerneldumpkey *kdk; + uint32_t dumpkeysize; + + dumpkeysize = roundup2(sizeof(*kdk) + encryptedkeysize, blocksize); + kdc = malloc(sizeof(*kdc) + dumpkeysize, M_EKCD, M_WAITOK | M_ZERO); + + arc4rand(kdc->kdc_iv, sizeof(kdc->kdc_iv), 0); + + kdc->kdc_encryption = encryption; + switch (kdc->kdc_encryption) { + case KERNELDUMP_ENC_AES_256_CBC: + if (rijndael_makeKey(&kdc->kdc_ki, DIR_ENCRYPT, 256, key) <= 0) + goto failed; + break; + default: + goto failed; + } + + kdc->kdc_dumpkeysize = dumpkeysize; + kdk = kdc->kdc_dumpkey; + kdk->kdk_encryption = kdc->kdc_encryption; + memcpy(kdk->kdk_iv, kdc->kdc_iv, sizeof(kdk->kdk_iv)); + kdk->kdk_encryptedkeysize = htod32(encryptedkeysize); + memcpy(kdk->kdk_encryptedkey, encryptedkey, encryptedkeysize); + + return (kdc); +failed: + explicit_bzero(kdc, sizeof(*kdc) + dumpkeysize); + free(kdc, M_EKCD); + return (NULL); +} +#endif /* EKCD */ + +int +kerneldumpcrypto_init(struct kerneldumpcrypto *kdc) +{ +#ifndef EKCD + return (0); +#else + uint8_t hash[SHA256_DIGEST_LENGTH]; + SHA256_CTX ctx; + struct kerneldumpkey *kdk; + int error; + + error = 0; + + if (kdc == NULL) + return (0); + + /* + * When a user enters ddb it can write a crash dump multiple times. + * Each time it should be encrypted using a different IV. + */ + SHA256_Init(&ctx); + SHA256_Update(&ctx, kdc->kdc_iv, sizeof(kdc->kdc_iv)); + SHA256_Final(hash, &ctx); + bcopy(hash, kdc->kdc_iv, sizeof(kdc->kdc_iv)); + + switch (kdc->kdc_encryption) { + case KERNELDUMP_ENC_AES_256_CBC: + if (rijndael_cipherInit(&kdc->kdc_ci, MODE_CBC, + kdc->kdc_iv) <= 0) { + error = EINVAL; + goto out; + } + break; + default: + error = EINVAL; + goto out; + } + + kdc->kdc_nextoffset = 0; + + kdk = kdc->kdc_dumpkey; + memcpy(kdk->kdk_iv, kdc->kdc_iv, sizeof(kdk->kdk_iv)); +out: + explicit_bzero(hash, sizeof(hash)); + return (error); +#endif +} + +uint32_t +kerneldumpcrypto_dumpkeysize(const struct kerneldumpcrypto *kdc) +{ + +#ifdef EKCD + if (kdc == NULL) + return (0); + return (kdc->kdc_dumpkeysize); +#else + return (0); +#endif +} + /* Registration of dumpers */ int -set_dumper(struct dumperinfo *di, const char *devname, struct thread *td) +set_dumper(struct dumperinfo *di, const char *devname, struct thread *td, + uint8_t encryption, const uint8_t *key, uint32_t encryptedkeysize, + const uint8_t *encryptedkey) { size_t wantcopy; int error; @@ -850,28 +972,56 @@ set_dumper(struct dumperinfo *di, const char *devname, struct thread *td) return (error); if (di == NULL) { - if (dumper.blockbuf != NULL) - free(dumper.blockbuf, M_DUMPER); - bzero(&dumper, sizeof(dumper)); - dumpdevname[0] = '\0'; - return (0); + error = 0; + goto cleanup; } if (dumper.dumper != NULL) return (EBUSY); dumper = *di; + dumper.blockbuf = NULL; + dumper.kdc = NULL; + + if (encryption != KERNELDUMP_ENC_NONE) { +#ifdef EKCD + dumper.kdc = kerneldumpcrypto_create(di->blocksize, encryption, + key, encryptedkeysize, encryptedkey); + if (dumper.kdc == NULL) { + error = EINVAL; + goto cleanup; + } +#else + error = EOPNOTSUPP; + goto cleanup; +#endif + } + wantcopy = strlcpy(dumpdevname, devname, sizeof(dumpdevname)); if (wantcopy >= sizeof(dumpdevname)) { printf("set_dumper: device name truncated from '%s' -> '%s'\n", devname, dumpdevname); } + dumper.blockbuf = malloc(di->blocksize, M_DUMPER, M_WAITOK | M_ZERO); return (0); +cleanup: +#ifdef EKCD + if (dumper.kdc != NULL) { + explicit_bzero(dumper.kdc, sizeof(*dumper.kdc) + + dumper.kdc->kdc_dumpkeysize); + free(dumper.kdc, M_EKCD); + } +#endif + if (dumper.blockbuf != NULL) { + explicit_bzero(dumper.blockbuf, dumper.blocksize); + free(dumper.blockbuf, M_DUMPER); + } + explicit_bzero(&dumper, sizeof(dumper)); + dumpdevname[0] = '\0'; + return (error); } -/* Call dumper with bounds checking. */ -int -dump_write(struct dumperinfo *di, void *virtual, vm_offset_t physical, - off_t offset, size_t length) +static int +dump_check_bounds(struct dumperinfo *di, off_t offset, size_t length) { if (length != 0 && (offset < di->mediaoffset || @@ -882,37 +1032,202 @@ dump_write(struct dumperinfo *di, void *virtual, vm_offset_t physical, (uintmax_t)length, (intmax_t)di->mediasize); return (ENOSPC); } + + return (0); +} + +#ifdef EKCD +static int +dump_encrypt(struct kerneldumpcrypto *kdc, uint8_t *buf, size_t size) +{ + + switch (kdc->kdc_encryption) { + case KERNELDUMP_ENC_AES_256_CBC: + if (rijndael_blockEncrypt(&kdc->kdc_ci, &kdc->kdc_ki, buf, + 8 * size, buf) <= 0) { + return (EIO); + } + if (rijndael_cipherInit(&kdc->kdc_ci, MODE_CBC, + buf + size - 16 /* IV size for AES-256-CBC */) <= 0) { + return (EIO); + } + break; + default: + return (EINVAL); + } + + return (0); +} + +/* Encrypt data and call dumper. */ +static int +dump_encrypted_write(struct dumperinfo *di, void *virtual, vm_offset_t physical, + off_t offset, size_t length) +{ + static uint8_t buf[KERNELDUMP_BUFFER_SIZE]; + struct kerneldumpcrypto *kdc; + int error; + size_t nbytes; + off_t nextoffset; + + kdc = di->kdc; + + error = dump_check_bounds(di, offset, length); + if (error != 0) + return (error); + + /* Signal completion. */ + if (virtual == NULL && physical == 0 && offset == 0 && length == 0) { + return (di->dumper(di->priv, virtual, physical, offset, + length)); + } + + /* Data have to be aligned to block size. */ + if ((length % di->blocksize) != 0) + return (EINVAL); + + /* + * Data have to be written continuously becase we're encrypting using + * CBC mode which has this assumption. + */ + if (kdc->kdc_nextoffset != 0 && kdc->kdc_nextoffset != offset) + return (EINVAL); + + nextoffset = offset + (off_t)length; + + while (length > 0) { + nbytes = MIN(length, sizeof(buf)); + bcopy(virtual, buf, nbytes); + + if (dump_encrypt(kdc, buf, nbytes) != 0) + return (EIO); + + error = di->dumper(di->priv, buf, physical, offset, nbytes); + if (error != 0) + return (error); + + offset += nbytes; + virtual = (void *)((uint8_t *)virtual + nbytes); + length -= nbytes; + } + + kdc->kdc_nextoffset = nextoffset; + + return (0); +} +#endif /* EKCD */ + +/* Call dumper with bounds checking. */ +static int +dump_raw_write(struct dumperinfo *di, void *virtual, vm_offset_t physical, + off_t offset, size_t length) +{ + int error; + + error = dump_check_bounds(di, offset, length); + if (error != 0) + return (error); + return (di->dumper(di->priv, virtual, physical, offset, length)); } -/* Call dumper with bounds checking. */ int -dump_write_pad(struct dumperinfo *di, void *virtual, vm_offset_t physical, - off_t offset, size_t length, size_t *size) +dump_write(struct dumperinfo *di, void *virtual, vm_offset_t physical, + off_t offset, size_t length) +{ + +#ifdef EKCD + if (di->kdc != NULL) { + return (dump_encrypted_write(di, virtual, physical, offset, + length)); + } +#endif + + return (dump_raw_write(di, virtual, physical, offset, length)); +} + +static int +dump_pad(struct dumperinfo *di, void *virtual, size_t length, void **buf, + size_t *size) { - char *temp; - int ret; if (length > di->blocksize) return (ENOMEM); *size = di->blocksize; - if (length == di->blocksize) - temp = virtual; - else { - temp = di->blockbuf; - memset(temp + length, 0, di->blocksize - length); - memcpy(temp, virtual, length); + if (length == di->blocksize) { + *buf = virtual; + } else { + *buf = di->blockbuf; + memcpy(*buf, virtual, length); + memset((uint8_t *)*buf + length, 0, di->blocksize - length); } - ret = dump_write(di, temp, physical, offset, *size); + return (0); +} + +static int +dump_raw_write_pad(struct dumperinfo *di, void *virtual, vm_offset_t physical, + off_t offset, size_t length, size_t *size) +{ + void *buf; + int error; + + error = dump_pad(di, virtual, length, &buf, size); + if (error != 0) + return (error); + + return (dump_raw_write(di, buf, physical, offset, *size)); +} + +int +dump_write_pad(struct dumperinfo *di, void *virtual, vm_offset_t physical, + off_t offset, size_t length, size_t *size) +{ + void *buf; + int error; + + error = dump_pad(di, virtual, length, &buf, size); + if (error != 0) + return (error); + + return (dump_write(di, buf, physical, offset, *size)); +} + +int +dump_write_header(struct dumperinfo *di, struct kerneldumpheader *kdh, + vm_offset_t physical, off_t offset) +{ + size_t size; + int ret; + + ret = dump_raw_write_pad(di, kdh, physical, offset, sizeof(*kdh), + &size); + if (ret == 0 && size != di->blocksize) + ret = EINVAL; return (ret); } +int +dump_write_key(struct dumperinfo *di, vm_offset_t physical, off_t offset) +{ +#ifndef EKCD + return (0); +#else /* EKCD */ + struct kerneldumpcrypto *kdc; + + kdc = di->kdc; + if (kdc == NULL) + return (0); + + return (dump_raw_write(di, kdc->kdc_dumpkey, physical, offset, + kdc->kdc_dumpkeysize)); +#endif /* !EKCD */ +} void mkdumpheader(struct kerneldumpheader *kdh, char *magic, uint32_t archver, - uint64_t dumplen, uint32_t blksz) + uint64_t dumplen, uint32_t dumpkeysize, uint32_t blksz) { bzero(kdh, sizeof(*kdh)); @@ -922,6 +1237,7 @@ mkdumpheader(struct kerneldumpheader *kdh, char *magic, uint32_t archver, kdh->architectureversion = htod32(archver); kdh->dumplength = htod64(dumplen); kdh->dumptime = htod64(time_second); + kdh->dumpkeysize = htod32(dumpkeysize); kdh->blocksize = htod32(blksz); strlcpy(kdh->hostname, prison0.pr_hostname, sizeof(kdh->hostname)); strlcpy(kdh->versionstring, version, sizeof(kdh->versionstring)); diff --git a/sys/mips/mips/minidump_machdep.c b/sys/mips/mips/minidump_machdep.c index d9e3b47c555e..c0dba4c5da6d 100644 --- a/sys/mips/mips/minidump_machdep.c +++ b/sys/mips/mips/minidump_machdep.c @@ -219,15 +219,22 @@ minidumpsys(struct dumperinfo *di) dumpsize += PAGE_SIZE; /* Determine dump offset on device. */ - if (di->mediasize < SIZEOF_METADATA + dumpsize + sizeof(kdh) * 2) { + if (di->mediasize < SIZEOF_METADATA + dumpsize + di->blocksize * 2 + + kerneldumpcrypto_dumpkeysize(di->kdc)) { error = ENOSPC; goto fail; } origdumplo = dumplo = di->mediaoffset + di->mediasize - dumpsize; - dumplo -= sizeof(kdh) * 2; + dumplo -= di->blocksize * 2; + dumplo -= kerneldumpcrypto_dumpkeysize(di->kdc); progress = dumpsize; + /* Initialize kernel dump crypto. */ + error = kerneldumpcrypto_init(di->kdc); + if (error) + goto fail; + /* Initialize mdhdr */ bzero(&mdhdr, sizeof(mdhdr)); strcpy(mdhdr.magic, MINIDUMP_MAGIC); @@ -238,17 +245,23 @@ minidumpsys(struct dumperinfo *di) mdhdr.kernbase = VM_MIN_KERNEL_ADDRESS; mkdumpheader(&kdh, KERNELDUMPMAGIC, KERNELDUMP_MIPS_VERSION, dumpsize, - di->blocksize); + kerneldumpcrypto_dumpkeysize(di->kdc), di->blocksize); printf("Physical memory: %ju MB\n", (uintmax_t)ptoa((uintmax_t)physmem) / 1048576); printf("Dumping %llu MB:", (long long)dumpsize >> 20); /* Dump leader */ - error = dump_write(di, &kdh, 0, dumplo, sizeof(kdh)); + error = dump_write_header(di, &kdh, 0, dumplo); if (error) goto fail; - dumplo += sizeof(kdh); + dumplo += di->blocksize; + + /* Dump key */ + error = dump_write_key(di, 0, dumplo); + if (error) + goto fail; + dumplo += kerneldumpcrypto_dumpkeysize(di->kdc); /* Dump my header */ bzero(tmpbuffer, sizeof(tmpbuffer)); @@ -316,10 +329,10 @@ minidumpsys(struct dumperinfo *di) } /* Dump trailer */ - error = dump_write(di, &kdh, 0, dumplo, sizeof(kdh)); + error = dump_write_header(di, &kdh, 0, dumplo); if (error) goto fail; - dumplo += sizeof(kdh); + dumplo += di->blocksize; /* Signal completion, signoff and exit stage left. */ dump_write(di, NULL, 0, 0, 0); diff --git a/sys/sparc64/sparc64/dump_machdep.c b/sys/sparc64/sparc64/dump_machdep.c index 9cb1aabd132c..6dc1fa425b93 100644 --- a/sys/sparc64/sparc64/dump_machdep.c +++ b/sys/sparc64/sparc64/dump_machdep.c @@ -94,7 +94,8 @@ dumpsys(struct dumperinfo *di) DEV_BSIZE); size += hdrsize; - totsize = size + 2 * sizeof(kdh); + totsize = size + 2 * di->blocksize + + kerneldumpcrypto_dumpkeysize(di->kdc); if (totsize > di->mediasize) { printf("Insufficient space on device (need %ld, have %ld), " "refusing to dump.\n", (long)totsize, @@ -106,16 +107,27 @@ dumpsys(struct dumperinfo *di) /* Determine dump offset on device. */ dumplo = di->mediaoffset + di->mediasize - totsize; + /* Initialize kernel dump crypto. */ + error = kerneldumpcrypto_init(di->kdc); + if (error) + goto fail; + mkdumpheader(&kdh, KERNELDUMPMAGIC, KERNELDUMP_SPARC64_VERSION, size, - di->blocksize); + kerneldumpcrypto_dumpkeysize(di->kdc), di->blocksize); printf("Dumping %lu MB (%d chunks)\n", (u_long)(size >> 20), nreg); /* Dump leader */ - error = dump_write(di, &kdh, 0, dumplo, sizeof(kdh)); + error = dump_write_header(di, &kdh, 0, dumplo); if (error) goto fail; - dumplo += sizeof(kdh); + dumplo += di->blocksize; + + /* Dump key */ + error = dump_write_key(di, 0, dumplo); + if (error) + goto fail; + dumplo += kerneldumpcrypto_dumpkeysize(di->kdc); /* Dump the private header. */ hdr.dh_hdr_size = hdrsize; @@ -143,9 +155,10 @@ dumpsys(struct dumperinfo *di) goto fail; /* Dump trailer */ - error = dump_write(di, &kdh, 0, dumplo, sizeof(kdh)); + error = dump_write_header(di, &kdh, 0, dumplo); if (error) goto fail; + dumplo += di->blocksize; /* Signal completion, signoff and exit stage left. */ dump_write(di, NULL, 0, 0, 0); diff --git a/sys/sys/conf.h b/sys/sys/conf.h index 2bfc14f56e4f..4cfb74476b0a 100644 --- a/sys/sys/conf.h +++ b/sys/sys/conf.h @@ -325,6 +325,8 @@ int dev_stdclone(char *_name, char **_namep, const char *_stem, int *_unit); EVENTHANDLER_DECLARE(dev_clone, dev_clone_fn); /* Stuff relating to kernel-dump */ +struct kerneldumpcrypto; +struct kerneldumpheader; struct dumperinfo { dumper_t *dumper; /* Dumping function. */ @@ -334,12 +336,18 @@ struct dumperinfo { off_t mediaoffset; /* Initial offset in bytes. */ off_t mediasize; /* Space available in bytes. */ void *blockbuf; /* Buffer for padding shorter dump blocks */ + struct kerneldumpcrypto *kdc; /* Kernel dump crypto. */ }; -int set_dumper(struct dumperinfo *, 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, + const uint8_t *encryptedkey); int dump_write(struct dumperinfo *, void *, vm_offset_t, off_t, size_t); int dump_write_pad(struct dumperinfo *, void *, vm_offset_t, off_t, size_t, size_t *); +int dump_write_header(struct dumperinfo *di, struct kerneldumpheader *kdh, + vm_offset_t physical, off_t offset); +int dump_write_key(struct dumperinfo *di, vm_offset_t physical, off_t offset); int doadump(boolean_t); extern int dumping; /* system is dumping */ diff --git a/sys/sys/disk.h b/sys/sys/disk.h index 6b35d748ce7e..873efd1ee213 100644 --- a/sys/sys/disk.h +++ b/sys/sys/disk.h @@ -14,6 +14,7 @@ #define _SYS_DISK_H_ #include +#include #include #include @@ -54,7 +55,7 @@ void disk_err(struct bio *bp, const char *what, int blkdone, int nl); * disk label formats. Don't use it unless you have to. */ -#define DIOCSKERNELDUMP _IOW('d', 133, u_int) /* Set/Clear kernel dumps */ +#define DIOCSKERNELDUMP_FREEBSD11 _IOW('d', 133, u_int) /* * Enable/Disable (the argument is boolean) the device for kernel * core dumps. @@ -139,4 +140,16 @@ struct diocgattr_arg { #define DIOCZONECMD _IOWR('d', 143, struct disk_zone_args) +struct diocskerneldump_arg { + uint8_t kda_enable; + uint8_t kda_encryption; + uint8_t kda_key[KERNELDUMP_KEY_MAX_SIZE]; + uint32_t kda_encryptedkeysize; + uint8_t *kda_encryptedkey; +}; +#define DIOCSKERNELDUMP _IOW('d', 144, struct diocskerneldump_arg) + /* + * Enable/Disable the device for kernel core dumps. + */ + #endif /* _SYS_DISK_H_ */ diff --git a/sys/sys/kerneldump.h b/sys/sys/kerneldump.h index 13bef698ffcb..3fb6a7ad18bf 100644 --- a/sys/sys/kerneldump.h +++ b/sys/sys/kerneldump.h @@ -38,6 +38,9 @@ #ifndef _SYS_KERNELDUMP_H #define _SYS_KERNELDUMP_H +#include +#include + #include #if BYTE_ORDER == LITTLE_ENDIAN @@ -52,6 +55,14 @@ #define htod64(x) (x) #endif +#define KERNELDUMP_ENC_NONE 0 +#define KERNELDUMP_ENC_AES_256_CBC 1 + +#define KERNELDUMP_BUFFER_SIZE 1024 +#define KERNELDUMP_IV_MAX_SIZE 32 +#define KERNELDUMP_KEY_MAX_SIZE 64 +#define KERNELDUMP_ENCKEY_MAX_SIZE (16384 / 8) + /* * All uintX_t fields are in dump byte order, which is the same as * network byte order. Use the macros defined above to read or @@ -64,8 +75,8 @@ struct kerneldumpheader { #define KERNELDUMPMAGIC_CLEARED "Cleared Kernel Dump" char architecture[12]; uint32_t version; -#define KERNELDUMPVERSION 1 -#define KERNELDUMP_TEXT_VERSION 1 +#define KERNELDUMPVERSION 2 +#define KERNELDUMP_TEXT_VERSION 2 uint32_t architectureversion; #define KERNELDUMP_AARCH64_VERSION 1 #define KERNELDUMP_AMD64_VERSION 2 @@ -77,13 +88,21 @@ struct kerneldumpheader { #define KERNELDUMP_SPARC64_VERSION 1 uint64_t dumplength; /* excl headers */ uint64_t dumptime; + uint32_t dumpkeysize; uint32_t blocksize; char hostname[64]; char versionstring[192]; - char panicstring[192]; + char panicstring[188]; uint32_t parity; }; +struct kerneldumpkey { + uint8_t kdk_encryption; + uint8_t kdk_iv[KERNELDUMP_IV_MAX_SIZE]; + uint32_t kdk_encryptedkeysize; + uint8_t kdk_encryptedkey[]; +} __packed; + /* * Parity calculation is endian insensitive. */ @@ -106,8 +125,11 @@ struct dump_pa { vm_paddr_t pa_size; }; +int kerneldumpcrypto_init(struct kerneldumpcrypto *kdc); +uint32_t kerneldumpcrypto_dumpkeysize(const struct kerneldumpcrypto *kdc); + void mkdumpheader(struct kerneldumpheader *kdh, char *magic, uint32_t archver, - uint64_t dumplen, uint32_t blksz); + uint64_t dumplen, uint32_t dumpkeysize, uint32_t blksz); int dumpsys_generic(struct dumperinfo *); @@ -115,6 +137,7 @@ void dumpsys_map_chunk(vm_paddr_t, size_t, void **); typedef int dumpsys_callback_t(struct dump_pa *, int, void *); int dumpsys_foreach_chunk(dumpsys_callback_t, void *); int dumpsys_cb_dumpdata(struct dump_pa *, int, void *); +int dumpsys_buf_seek(struct dumperinfo *, size_t); int dumpsys_buf_write(struct dumperinfo *, char *, size_t); int dumpsys_buf_flush(struct dumperinfo *);