Index: head/sbin/decryptcore/decryptcore.8 =================================================================== --- head/sbin/decryptcore/decryptcore.8 (revision 313458) +++ head/sbin/decryptcore/decryptcore.8 (revision 313459) @@ -1,112 +1,122 @@ .\" 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 13, 2016 +.Dd February 9, 2017 .Dt DECRYPTCORE 8 .Os .Sh NAME .Nm decryptcore .Nd "decrypt a core dump of the operating system" .Sh SYNOPSIS .Nm -.Op Fl Lv +.Op Fl fLv .Fl p Ar privatekeyfile .Fl k Ar keyfile .Fl e Ar encryptedcore .Fl c Ar core .Nm -.Op Fl Lv +.Op Fl fLv .Op Fl d Ar crashdir .Fl p Ar privatekeyfile .Fl n Ar dumpnr .Sh DESCRIPTION The .Nm utility first decrypts .Ar keyfile using .Ar privatekeyfile and then uses the resulting key to decrypt .Ar encryptedcore saved by .Xr savecore 8 . The 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 +By default +.Nm +does not overwrite an old core dump as a user might want to store the core +somewhere else for the future. +This behaviour can be changed using the +.Fl f +flag. +.Pp The .Nm utility can be started with the following command line arguments: .Bl -tag -width ".Fl e Ar encryptedcore" +.It Fl f +Remove a decryped core dump if it already exists. .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 .Ex -std .Sh SEE ALSO .Xr kgdb 1 , .Xr capsicum 4 , .Xr dumpon 8 , .Xr savecore 8 , .Xr syslogd 8 .Sh AUTHORS The .Nm utility was implemented by .An Konrad Witaszczyk Aq Mt def@FreeBSD.org . Index: head/sbin/decryptcore/decryptcore.c =================================================================== --- head/sbin/decryptcore/decryptcore.c (revision 313458) +++ head/sbin/decryptcore/decryptcore.c (revision 313459) @@ -1,373 +1,387 @@ /*- * 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 "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"); + "usage: decryptcore [-fLv] -p privatekeyfile -k keyfile -e encryptedcore -c core\n" + " decryptcore [-fLv] [-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) +decrypt(int ofd, const char *privkeyfile, const char *keyfile, + const char *input) { 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; + int ifd, kfd, olen, privkeysize; ssize_t bytes; pid_t pid; + PJDLOG_ASSERT(ofd >= 0); 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"); + close(ofd); return (false); } - if (pid > 0) + if (pid > 0) { + close(ofd); 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; } 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 && write(ofd, buf, olen) != olen) { - pjdlog_errno(LOG_ERR, "Unable to write data to %s", - output); + pjdlog_errno(LOG_ERR, "Unable to write core"); 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]; const char *crashdir, *dumpnr, *privatekey; - int ch, debug; + int ch, debug, error, ofd; size_t ii; - bool usesyslog; + bool force, usesyslog; + error = 1; + pjdlog_init(PJDLOG_MODE_STD); pjdlog_prefix_set("(decryptcore) "); debug = 0; *core = '\0'; crashdir = NULL; dumpnr = NULL; *encryptedcore = '\0'; + force = false; *keyfile = '\0'; privatekey = NULL; usesyslog = false; - while ((ch = getopt(argc, argv, "Lc:d:e:k:n:p:v")) != -1) { + while ((ch = getopt(argc, argv, "Lc:d:e:fk:n:p:v")) != -1) { switch (ch) { case 'L': usesyslog = true; break; case 'c': if (strlcpy(core, optarg, sizeof(core)) >= sizeof(core)) pjdlog_exitx(1, "Core file path is too long."); break; case 'd': crashdir = optarg; break; case 'e': if (strlcpy(encryptedcore, optarg, sizeof(encryptedcore)) >= sizeof(encryptedcore)) { pjdlog_exitx(1, "Encrypted core file path is too long."); } break; + case 'f': + force = true; + break; case 'k': if (strlcpy(keyfile, optarg, sizeof(keyfile)) >= sizeof(keyfile)) { pjdlog_exitx(1, "Key file path is too long."); } 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 (force && unlink(core) == -1 && errno != ENOENT) { + pjdlog_errno(LOG_ERR, "Unable to remove old core"); + goto out; + } + ofd = open(core, O_WRONLY | O_CREAT | O_EXCL, 0600); + if (ofd == -1) { + pjdlog_errno(LOG_ERR, "Unable to open %s", core); + goto out; + } + + if (!decrypt(ofd, privatekey, keyfile, encryptedcore)) { if (unlink(core) == -1 && errno != ENOENT) - pjdlog_exit(1, "Unable to remove core"); - exit(1); + pjdlog_errno(LOG_ERR, "Unable to remove core"); + goto out; } + error = 0; +out: pjdlog_fini(); - - exit(0); + exit(error); }