Changeset View
Standalone View
sbin/decryptcore/decryptcore.c
- This file was added.
/*- | |||||
* Copyright (c) 2015 Konrad Witaszczyk <def@FreeBSD.org> | |||||
* 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 <sys/cdefs.h> | |||||
oshogbo: Missing cdefs and __FBSDID. | |||||
__FBSDID("$FreeBSD$"); | |||||
#include <sys/types.h> | |||||
#include <sys/capsicum.h> | |||||
#include <sys/endian.h> | |||||
#include <sys/kerneldump.h> | |||||
#include <sys/stat.h> | |||||
#include <sys/sysctl.h> | |||||
#include <sys/wait.h> | |||||
#include <openssl/evp.h> | |||||
#include <openssl/pem.h> | |||||
#include <openssl/rsa.h> | |||||
#include <openssl/engine.h> | |||||
#include <ctype.h> | |||||
#include <fcntl.h> | |||||
#include <pjdlog.h> | |||||
#include <stdbool.h> | |||||
#include <stdlib.h> | |||||
#include <string.h> | |||||
#include <unistd.h> | |||||
cemUnsubmitted Done Inline ActionsI believe openssl headers and pjdlog should be in block(s) following standard C headers. cem: I believe openssl headers and pjdlog should be in block(s) following standard C headers. | |||||
#define DECRYPTCORE_CRASHDIR "/var/crash" | |||||
static void | |||||
usage(void) | |||||
{ | |||||
pjdlog_exitx(1, | |||||
"usage: decryptcore [-Lv] -p privatekey -k key -e encryptedcore -c core\n" | |||||
" decryptcore [-Lv] [-d crashdir] -p privatekey -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) | |||||
{ | |||||
uint8_t *buf, *p; | |||||
struct kerneldumpkey *kdk; | |||||
uint32_t encryptedkeysize; | |||||
ssize_t size; | |||||
size_t kdksize, bytes; | |||||
PJDLOG_ASSERT(kfd >= 0); | |||||
buf = NULL; | |||||
kdk = NULL; | |||||
kdksize = sizeof(*kdk); | |||||
kdk = calloc(1, kdksize); | |||||
if (kdk == NULL) { | |||||
pjdlog_errno(LOG_ERR, "Unable to allocate kernel dump key"); | |||||
goto failed; | |||||
} | |||||
bytes = sizeof(kdk->kdk_encryption) + sizeof(kdk->kdk_iv) + | |||||
sizeof(kdk->kdk_encryptedkeysize); | |||||
buf = calloc(1, bytes); | |||||
if (buf == NULL) { | |||||
pjdlog_errno(LOG_ERR, "Unable to allocate buffer"); | |||||
goto failed; | |||||
} | |||||
size = read(kfd, buf, bytes); | |||||
if (size == (ssize_t)bytes) { | |||||
p = buf; | |||||
kdk->kdk_encryption = *p; | |||||
p += sizeof(kdk->kdk_encryption); | |||||
bcopy(p, kdk->kdk_iv, sizeof(kdk->kdk_iv)); | |||||
p += sizeof(kdk->kdk_iv); | |||||
bcopy(p, &encryptedkeysize, sizeof(encryptedkeysize)); | |||||
kdk->kdk_encryptedkeysize = dtoh32(encryptedkeysize); | |||||
p += sizeof(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; | |||||
} | |||||
bytes += (size_t)kdk->kdk_encryptedkeysize; | |||||
size += read(kfd, &kdk->kdk_encryptedkey, | |||||
kdk->kdk_encryptedkeysize); | |||||
} | |||||
if (size != (ssize_t)bytes) { | |||||
pjdlog_errno(LOG_ERR, "Unable to read key"); | |||||
goto failed; | |||||
} | |||||
free(buf); | |||||
return (kdk); | |||||
failed: | |||||
free(buf); | |||||
free(kdk); | |||||
return (NULL); | |||||
} | |||||
static bool | |||||
decrypt(const char *privkeyfile, const char *keyfile, const char *input, | |||||
const char *output) | |||||
{ | |||||
uint8_t buf[2 * KERNELDUMP_BLOCK_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; | |||||
} | |||||
Done Inline ActionsShould we disable coredump at this point if not debugging? Otherwise it may defeat the purpose if the process crashes. delphij: Should we disable coredump at this point if not debugging? Otherwise it may defeat the purpose… | |||||
Done Inline ActionsEvery where where you using cap_enter it should be checked like that: oshogbo: Every where where you using cap_enter it should be checked like that:
cap_enter() < 0 && errno ! | |||||
Done Inline ActionsIt's assumed that disabling Capsicum doesn't disable a functionality which uses Capsicum. def: It's assumed that disabling Capsicum doesn't disable a functionality which uses Capsicum. | |||||
fp = fopen(privkeyfile, "r"); | |||||
if (fp == NULL) { | |||||
pjdlog_errno(LOG_ERR, "Unable to open %s", privkeyfile); | |||||
goto failed; | |||||
} | |||||
Done Inline ActionsIs there some reason that this section (193-199) is done after entering capability mode? (Or other words, can they be moved to before opening the files?) delphij: Is there some reason that this section (193-199) is done after entering capability mode? (Or… | |||||
Done Inline ActionsWhy do you want to increase TCB? Even if the code doesn't introduce any risk now, it may be modified in the future. I'd need to hear a convincing argument to agree on this one:) pjd: Why do you want to increase TCB? Even if the code doesn't introduce any risk now, it may be… | |||||
Done Inline ActionsAs Paweł said we don't want to perform any unnecessary operation before entering capability mode. def: As Paweł said we don't want to perform any unnecessary operation before entering capability… | |||||
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; | |||||
} | |||||
Done Inline ActionsPerhaps explicit_bzero(3)? delphij: Perhaps explicit_bzero(3)? | |||||
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) { | |||||
Done Inline ActionsThis construction is Okay, but if I was you, I would probably go with standard getopt_long which would avoid the hardcoding of parameters and allow more flexible command line options ordering. Just a suggestion. delphij: This construction is Okay, but if I was you, I would probably go with standard getopt_long… | |||||
Done Inline ActionsI'm fine with this suggestion, but I'd stick to regular getopt(3), which should be more than enough here. pjd: I'm fine with this suggestion, but I'd stick to regular getopt(3), which should be more than… | |||||
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': | |||||
Done Inline ActionsWhy just don't use oshogbo: Why just don't use
output = argv[3] ?
and so any.
Code would be a little bit smaller. | |||||
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); | |||||
} |
Missing cdefs and __FBSDID.