Changeset View
Changeset View
Standalone View
Standalone View
head/sys/kern/kern_shutdown.c
Show First 20 Lines • Show All 151 Lines • ▼ Show 20 Lines | |||||
MALLOC_DEFINE(M_EKCD, "ekcd", "Encrypted kernel crash dumps data"); | MALLOC_DEFINE(M_EKCD, "ekcd", "Encrypted kernel crash dumps data"); | ||||
struct kerneldumpcrypto { | struct kerneldumpcrypto { | ||||
uint8_t kdc_encryption; | uint8_t kdc_encryption; | ||||
uint8_t kdc_iv[KERNELDUMP_IV_MAX_SIZE]; | uint8_t kdc_iv[KERNELDUMP_IV_MAX_SIZE]; | ||||
keyInstance kdc_ki; | keyInstance kdc_ki; | ||||
cipherInstance kdc_ci; | cipherInstance kdc_ci; | ||||
off_t kdc_nextoffset; | |||||
uint32_t kdc_dumpkeysize; | uint32_t kdc_dumpkeysize; | ||||
struct kerneldumpkey kdc_dumpkey[]; | struct kerneldumpkey kdc_dumpkey[]; | ||||
}; | }; | ||||
#endif | #endif | ||||
/* | /* | ||||
* Variable panicstr contains argument to first call to panic; used as flag | * Variable panicstr contains argument to first call to panic; used as flag | ||||
* to indicate that the kernel has already called panic. | * to indicate that the kernel has already called panic. | ||||
▲ Show 20 Lines • Show All 757 Lines • ▼ Show 20 Lines | if (rijndael_cipherInit(&kdc->kdc_ci, MODE_CBC, | ||||
goto out; | goto out; | ||||
} | } | ||||
break; | break; | ||||
default: | default: | ||||
error = EINVAL; | error = EINVAL; | ||||
goto out; | goto out; | ||||
} | } | ||||
kdc->kdc_nextoffset = 0; | |||||
kdk = kdc->kdc_dumpkey; | kdk = kdc->kdc_dumpkey; | ||||
memcpy(kdk->kdk_iv, kdc->kdc_iv, sizeof(kdk->kdk_iv)); | memcpy(kdk->kdk_iv, kdc->kdc_iv, sizeof(kdk->kdk_iv)); | ||||
out: | out: | ||||
explicit_bzero(hash, sizeof(hash)); | explicit_bzero(hash, sizeof(hash)); | ||||
return (error); | return (error); | ||||
} | } | ||||
static uint32_t | static uint32_t | ||||
▲ Show 20 Lines • Show All 75 Lines • ▼ Show 20 Lines | dump_check_bounds(struct dumperinfo *di, off_t offset, size_t length) | ||||
if (length != 0 && (offset < di->mediaoffset || | if (length != 0 && (offset < di->mediaoffset || | ||||
offset - di->mediaoffset + length > di->mediasize)) { | offset - di->mediaoffset + length > di->mediasize)) { | ||||
printf("Attempt to write outside dump device boundaries.\n" | printf("Attempt to write outside dump device boundaries.\n" | ||||
"offset(%jd), mediaoffset(%jd), length(%ju), mediasize(%jd).\n", | "offset(%jd), mediaoffset(%jd), length(%ju), mediasize(%jd).\n", | ||||
(intmax_t)offset, (intmax_t)di->mediaoffset, | (intmax_t)offset, (intmax_t)di->mediaoffset, | ||||
(uintmax_t)length, (intmax_t)di->mediasize); | (uintmax_t)length, (intmax_t)di->mediasize); | ||||
return (ENOSPC); | return (ENOSPC); | ||||
} | } | ||||
if (length % di->blocksize != 0) { | |||||
printf("Attempt to write partial block of length %ju.\n", | |||||
(uintmax_t)length); | |||||
return (EINVAL); | |||||
} | |||||
if (offset % di->blocksize != 0) { | |||||
printf("Attempt to write at unaligned offset %jd.\n", | |||||
(intmax_t)offset); | |||||
return (EINVAL); | |||||
} | |||||
return (0); | return (0); | ||||
} | } | ||||
/* 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)); | |||||
} | |||||
#ifdef EKCD | #ifdef EKCD | ||||
static int | static int | ||||
dump_encrypt(struct kerneldumpcrypto *kdc, uint8_t *buf, size_t size) | dump_encrypt(struct kerneldumpcrypto *kdc, uint8_t *buf, size_t size) | ||||
{ | { | ||||
switch (kdc->kdc_encryption) { | switch (kdc->kdc_encryption) { | ||||
case KERNELDUMP_ENC_AES_256_CBC: | case KERNELDUMP_ENC_AES_256_CBC: | ||||
if (rijndael_blockEncrypt(&kdc->kdc_ci, &kdc->kdc_ki, buf, | if (rijndael_blockEncrypt(&kdc->kdc_ci, &kdc->kdc_ki, buf, | ||||
Show All 9 Lines | default: | ||||
return (EINVAL); | return (EINVAL); | ||||
} | } | ||||
return (0); | return (0); | ||||
} | } | ||||
/* Encrypt data and call dumper. */ | /* Encrypt data and call dumper. */ | ||||
static int | static int | ||||
dump_encrypted_write(struct dumperinfo *di, void *virtual, vm_offset_t physical, | dump_encrypted_write(struct dumperinfo *di, void *virtual, | ||||
off_t offset, size_t length) | vm_offset_t physical, off_t offset, size_t length) | ||||
{ | { | ||||
static uint8_t buf[KERNELDUMP_BUFFER_SIZE]; | static uint8_t buf[KERNELDUMP_BUFFER_SIZE]; | ||||
struct kerneldumpcrypto *kdc; | struct kerneldumpcrypto *kdc; | ||||
int error; | int error; | ||||
size_t nbytes; | size_t nbytes; | ||||
off_t nextoffset; | |||||
kdc = di->kdc; | 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) { | while (length > 0) { | ||||
nbytes = MIN(length, sizeof(buf)); | nbytes = MIN(length, sizeof(buf)); | ||||
bcopy(virtual, buf, nbytes); | bcopy(virtual, buf, nbytes); | ||||
if (dump_encrypt(kdc, buf, nbytes) != 0) | if (dump_encrypt(kdc, buf, nbytes) != 0) | ||||
return (EIO); | return (EIO); | ||||
error = di->dumper(di->priv, buf, physical, offset, nbytes); | error = dump_write(di, buf, physical, offset, nbytes); | ||||
if (error != 0) | if (error != 0) | ||||
return (error); | return (error); | ||||
offset += nbytes; | offset += nbytes; | ||||
virtual = (void *)((uint8_t *)virtual + nbytes); | virtual = (void *)((uint8_t *)virtual + nbytes); | ||||
length -= nbytes; | length -= nbytes; | ||||
} | } | ||||
kdc->kdc_nextoffset = nextoffset; | |||||
return (0); | return (0); | ||||
} | } | ||||
static int | static int | ||||
dump_write_key(struct dumperinfo *di, vm_offset_t physical, off_t offset) | dump_write_key(struct dumperinfo *di, vm_offset_t physical, off_t offset) | ||||
{ | { | ||||
struct kerneldumpcrypto *kdc; | struct kerneldumpcrypto *kdc; | ||||
kdc = di->kdc; | kdc = di->kdc; | ||||
if (kdc == NULL) | if (kdc == NULL) | ||||
return (0); | return (0); | ||||
return (dump_raw_write(di, kdc->kdc_dumpkey, physical, offset, | return (dump_write(di, kdc->kdc_dumpkey, physical, offset, | ||||
kdc->kdc_dumpkeysize)); | kdc->kdc_dumpkeysize)); | ||||
} | } | ||||
#endif /* EKCD */ | #endif /* EKCD */ | ||||
int | |||||
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 | static int | ||||
dump_write_header(struct dumperinfo *di, struct kerneldumpheader *kdh, | dump_write_header(struct dumperinfo *di, struct kerneldumpheader *kdh, | ||||
vm_offset_t physical, off_t offset) | vm_offset_t physical, off_t offset) | ||||
{ | { | ||||
void *buf; | void *buf; | ||||
size_t hdrsz; | size_t hdrsz; | ||||
hdrsz = sizeof(*kdh); | hdrsz = sizeof(*kdh); | ||||
if (hdrsz > di->blocksize) | if (hdrsz > di->blocksize) | ||||
return (ENOMEM); | return (ENOMEM); | ||||
if (hdrsz == di->blocksize) | if (hdrsz == di->blocksize) | ||||
buf = kdh; | buf = kdh; | ||||
else { | else { | ||||
buf = di->blockbuf; | buf = di->blockbuf; | ||||
memset(buf, 0, di->blocksize); | memset(buf, 0, di->blocksize); | ||||
memcpy(buf, kdh, hdrsz); | memcpy(buf, kdh, hdrsz); | ||||
} | } | ||||
return (dump_raw_write(di, buf, physical, offset, di->blocksize)); | return (dump_write(di, buf, physical, offset, di->blocksize)); | ||||
} | } | ||||
/* | /* | ||||
* Don't touch the first SIZEOF_METADATA bytes on the dump device. This is to | * Don't touch the first SIZEOF_METADATA bytes on the dump device. This is to | ||||
* protect us from metadata and metadata from us. | * protect us from metadata and metadata from us. | ||||
*/ | */ | ||||
#define SIZEOF_METADATA (64 * 1024) | #define SIZEOF_METADATA (64 * 1024) | ||||
/* | /* | ||||
* Do some preliminary setup for a kernel dump: verify that we have enough space | * Do some preliminary setup for a kernel dump: verify that we have enough space | ||||
* on the dump device, write the leading header, and optionally write the crypto | * on the dump device, write the leading header, and optionally write the crypto | ||||
* key. | * key. | ||||
*/ | */ | ||||
int | int | ||||
dump_start(struct dumperinfo *di, struct kerneldumpheader *kdh, off_t *dumplop) | dump_start(struct dumperinfo *di, struct kerneldumpheader *kdh) | ||||
{ | { | ||||
uint64_t dumpsize; | uint64_t dumpsize; | ||||
uint32_t keysize; | uint32_t keysize; | ||||
int error; | int error; | ||||
#ifdef EKCD | #ifdef EKCD | ||||
error = kerneldumpcrypto_init(di->kdc); | error = kerneldumpcrypto_init(di->kdc); | ||||
if (error != 0) | if (error != 0) | ||||
return (error); | return (error); | ||||
keysize = kerneldumpcrypto_dumpkeysize(di->kdc); | keysize = kerneldumpcrypto_dumpkeysize(di->kdc); | ||||
#else | #else | ||||
keysize = 0; | keysize = 0; | ||||
#endif | #endif | ||||
dumpsize = dtoh64(kdh->dumplength) + 2 * di->blocksize + keysize; | dumpsize = dtoh64(kdh->dumplength) + 2 * di->blocksize + keysize; | ||||
if (di->mediasize < SIZEOF_METADATA + dumpsize) | if (di->mediasize < SIZEOF_METADATA + dumpsize) | ||||
return (E2BIG); | return (E2BIG); | ||||
*dumplop = di->mediaoffset + di->mediasize - dumpsize; | di->dumpoff = di->mediaoffset + di->mediasize - dumpsize; | ||||
error = dump_write_header(di, kdh, 0, *dumplop); | error = dump_write_header(di, kdh, 0, di->dumpoff); | ||||
if (error != 0) | if (error != 0) | ||||
return (error); | return (error); | ||||
*dumplop += di->blocksize; | di->dumpoff += di->blocksize; | ||||
#ifdef EKCD | #ifdef EKCD | ||||
error = dump_write_key(di, 0, *dumplop); | error = dump_write_key(di, 0, di->dumpoff); | ||||
if (error != 0) | if (error != 0) | ||||
return (error); | return (error); | ||||
*dumplop += keysize; | di->dumpoff += keysize; | ||||
#endif | #endif | ||||
return (0); | return (0); | ||||
} | } | ||||
/* Write to the dump device at the current dump offset. */ | |||||
int | |||||
dump_append(struct dumperinfo *di, void *virtual, vm_offset_t physical, | |||||
size_t length) | |||||
{ | |||||
int error; | |||||
#ifdef EKCD | |||||
if (di->kdc != NULL) | |||||
error = dump_encrypted_write(di, virtual, physical, di->dumpoff, | |||||
length); | |||||
else | |||||
#endif | |||||
error = dump_write(di, virtual, physical, di->dumpoff, length); | |||||
if (error == 0) | |||||
di->dumpoff += length; | |||||
return (error); | |||||
} | |||||
/* Perform a raw write to the dump device at the specified offset. */ | |||||
int | |||||
dump_write(struct dumperinfo *di, void *virtual, vm_offset_t physical, | |||||
off_t offset, size_t length) | |||||
{ | |||||
int error; | |||||
error = dump_check_bounds(di, offset, length); | |||||
if (error != 0) | |||||
return (error); | |||||
return (di->dumper(di->priv, virtual, physical, offset, length)); | |||||
} | |||||
/* | /* | ||||
* Write the trailing kernel dump header and signal to the lower layers that the | * Write the trailing kernel dump header and signal to the lower layers that the | ||||
* dump has completed. | * dump has completed. | ||||
*/ | */ | ||||
int | int | ||||
dump_finish(struct dumperinfo *di, struct kerneldumpheader *kdh, off_t dumplo) | dump_finish(struct dumperinfo *di, struct kerneldumpheader *kdh) | ||||
{ | { | ||||
int error; | int error; | ||||
error = dump_write_header(di, kdh, 0, dumplo); | error = dump_write_header(di, kdh, 0, di->dumpoff); | ||||
if (error != 0) | if (error != 0) | ||||
return (error); | return (error); | ||||
(void)dump_write(di, NULL, 0, 0, 0); | (void)dump_write(di, NULL, 0, 0, 0); | ||||
return (0); | return (0); | ||||
} | } | ||||
void | void | ||||
Show All 37 Lines |