Add repair command and API for repairing known LUKS header problems.

This commit is contained in:
Milan Broz
2012-04-02 21:18:22 +02:00
parent 9511c91a79
commit bd047d03ef
9 changed files with 172 additions and 44 deletions

View File

@@ -1,5 +1,6 @@
2012-03-16 Milan Broz <gmazyland@gmail.com> 2012-03-16 Milan Broz <gmazyland@gmail.com>
* Add --keyfile-offset and --new-keyfile-offset parameters to API and CLI. * Add --keyfile-offset and --new-keyfile-offset parameters to API and CLI.
* Add repair command and crypt_repair() for known LUKS metadata problems repair.
2012-03-16 Milan Broz <mbroz@redhat.com> 2012-03-16 Milan Broz <mbroz@redhat.com>
* Unify password verification option. * Unify password verification option.

View File

@@ -391,6 +391,20 @@ int crypt_load(struct crypt_device *cd,
const char *requested_type, const char *requested_type,
void *params); void *params);
/**
* Try to repair crypt device on-disk header if invalid
*
* @param cd crypt device handle
* @param requested_type - use @e NULL for all known
* @param params crypt type specific parameters (see @link crypt_type @endlink)
*
* @returns 0 on success or negative errno value otherwise.
*
*/
int crypt_repair(struct crypt_device *cd,
const char *requested_type,
void *params __attribute__((unused)));
/** /**
* Resize crypt device * Resize crypt device
* *

View File

@@ -17,6 +17,7 @@ CRYPTSETUP_1.0 {
crypt_memory_lock; crypt_memory_lock;
crypt_format; crypt_format;
crypt_load; crypt_load;
crypt_repair;
crypt_resize; crypt_resize;
crypt_suspend; crypt_suspend;
crypt_resume_by_passphrase; crypt_resume_by_passphrase;

View File

@@ -149,7 +149,7 @@ int LUKS_hdr_backup(
return -EINVAL; return -EINVAL;
} }
r = LUKS_read_phdr(device, hdr, 1, ctx); r = LUKS_read_phdr(device, hdr, 1, 0, ctx);
if (r) if (r)
return r; return r;
@@ -245,7 +245,7 @@ int LUKS_hdr_restore(
} }
close(devfd); close(devfd);
r = LUKS_read_phdr(device, hdr, 0, ctx); r = LUKS_read_phdr(device, hdr, 0, 0, ctx);
if (r == 0) { if (r == 0) {
log_dbg("Device %s already contains LUKS header, checking UUID and offset.", device); log_dbg("Device %s already contains LUKS header, checking UUID and offset.", device);
if(hdr->payloadOffset != hdr_file.payloadOffset || if(hdr->payloadOffset != hdr_file.payloadOffset ||
@@ -288,7 +288,7 @@ int LUKS_hdr_restore(
close(devfd); close(devfd);
/* Be sure to reload new data */ /* Be sure to reload new data */
r = LUKS_read_phdr(device, hdr, 1, ctx); r = LUKS_read_phdr(device, hdr, 1, 0, ctx);
out: out:
if (devfd != -1) if (devfd != -1)
close(devfd); close(devfd);
@@ -296,23 +296,35 @@ out:
return r; return r;
} }
/* This routine should do some just basic recovery for known problems. */
static int _keyslot_repair(const char *device, struct luks_phdr *phdr, struct crypt_device *ctx) static int _keyslot_repair(const char *device, struct luks_phdr *phdr, struct crypt_device *ctx)
{ {
struct luks_phdr temp_phdr; struct luks_phdr temp_phdr;
const unsigned char *sector = (const unsigned char*)phdr; const unsigned char *sector = (const unsigned char*)phdr;
struct volume_key *vk; struct volume_key *vk;
uint64_t PBKDF2_per_sec = 1; uint64_t PBKDF2_per_sec = 1;
int i, bad; int i, bad, r, need_write = 0;
// FIXME check keyBytes if (phdr->keyBytes != 16 && phdr->keyBytes != 32) {
log_err(ctx, _("Non standard key size, manual repair required.\n"));
return -EINVAL;
}
vk = crypt_alloc_volume_key(phdr->keyBytes, NULL); vk = crypt_alloc_volume_key(phdr->keyBytes, NULL);
// FIXME check cipher, cipher_mode, hash, uuid, payloadOffset log_verbose(ctx, _("Repairing keyslots.\n"));
LUKS_generate_phdr(&temp_phdr, vk, phdr->cipherName, phdr->cipherMode,
log_dbg("Generating second header with the same parameters for check.");
/* cipherName, cipherMode, hashSpec, uuid are already null terminated */
/* payloadOffset - cannot check */
r = LUKS_generate_phdr(&temp_phdr, vk, phdr->cipherName, phdr->cipherMode,
phdr->hashSpec,phdr->uuid, LUKS_STRIPES, phdr->hashSpec,phdr->uuid, LUKS_STRIPES,
phdr->payloadOffset, 0, phdr->payloadOffset, 0,
1, &PBKDF2_per_sec, 1, &PBKDF2_per_sec,
"/dev/null", ctx); "/dev/null", ctx);
if (r < 0) {
log_err(ctx, _("Repair failed."));
goto out;
}
for(i = 0; i < LUKS_NUMKEYS; ++i) { for(i = 0; i < LUKS_NUMKEYS; ++i) {
bad = 0; bad = 0;
@@ -332,7 +344,7 @@ static int _keyslot_repair(const char *device, struct luks_phdr *phdr, struct cr
bad = 1; bad = 1;
} }
/* if enabled, do not try to fix it */ /* if enabled, do not try to wipe salt */
if (phdr->keyblock[i].active != LUKS_KEY_ENABLED) { if (phdr->keyblock[i].active != LUKS_KEY_ENABLED) {
/* Known case - MSDOS partition table signature */ /* Known case - MSDOS partition table signature */
if (i == 6 && sector[0x1fe] == 0x55 && sector[0x1ff] == 0xaa) { if (i == 6 && sector[0x1fe] == 0x55 && sector[0x1ff] == 0xaa) {
@@ -347,17 +359,25 @@ static int _keyslot_repair(const char *device, struct luks_phdr *phdr, struct cr
phdr->keyblock[i].passwordIterations = 0; phdr->keyblock[i].passwordIterations = 0;
} }
} }
if (bad)
need_write = 1;
} }
if (need_write) {
log_verbose(ctx, _("Writing LUKS header to disk.\n"));
r = LUKS_write_phdr(device, phdr, ctx);
}
out:
crypt_free_volume_key(vk); crypt_free_volume_key(vk);
memset(&temp_phdr, 0, sizeof(temp_phdr)); memset(&temp_phdr, 0, sizeof(temp_phdr));
return r;
return LUKS_write_phdr(device, phdr, ctx);
} }
static int _check_and_convert_hdr(const char *device, static int _check_and_convert_hdr(const char *device,
struct luks_phdr *hdr, struct luks_phdr *hdr,
int require_luks_device, int require_luks_device,
int repair,
struct crypt_device *ctx) struct crypt_device *ctx)
{ {
int r = 0; int r = 0;
@@ -368,39 +388,44 @@ static int _check_and_convert_hdr(const char *device,
log_dbg("LUKS header not detected."); log_dbg("LUKS header not detected.");
if (require_luks_device) if (require_luks_device)
log_err(ctx, _("Device %s is not a valid LUKS device.\n"), device); log_err(ctx, _("Device %s is not a valid LUKS device.\n"), device);
r = -EINVAL; return -EINVAL;
} else if((hdr->version = ntohs(hdr->version)) != 1) { /* Convert every uint16/32_t item from network byte order */ } else if((hdr->version = ntohs(hdr->version)) != 1) { /* Convert every uint16/32_t item from network byte order */
log_err(ctx, _("Unsupported LUKS version %d.\n"), hdr->version); log_err(ctx, _("Unsupported LUKS version %d.\n"), hdr->version);
r = -EINVAL; return -EINVAL;
} else if (PBKDF2_HMAC_ready(hdr->hashSpec) < 0) { }
hdr->hashSpec[LUKS_HASHSPEC_L - 1] = '\0';
if (PBKDF2_HMAC_ready(hdr->hashSpec) < 0) {
log_err(ctx, _("Requested LUKS hash %s is not supported.\n"), hdr->hashSpec); log_err(ctx, _("Requested LUKS hash %s is not supported.\n"), hdr->hashSpec);
r = -EINVAL; return -EINVAL;
} else { }
hdr->payloadOffset = ntohl(hdr->payloadOffset);
hdr->keyBytes = ntohl(hdr->keyBytes);
hdr->mkDigestIterations = ntohl(hdr->mkDigestIterations);
for(i = 0; i < LUKS_NUMKEYS; ++i) { /* Header detected */
hdr->keyblock[i].active = ntohl(hdr->keyblock[i].active); hdr->payloadOffset = ntohl(hdr->payloadOffset);
hdr->keyblock[i].passwordIterations = ntohl(hdr->keyblock[i].passwordIterations); hdr->keyBytes = ntohl(hdr->keyBytes);
hdr->keyblock[i].keyMaterialOffset = ntohl(hdr->keyblock[i].keyMaterialOffset); hdr->mkDigestIterations = ntohl(hdr->mkDigestIterations);
hdr->keyblock[i].stripes = ntohl(hdr->keyblock[i].stripes);
if (LUKS_check_keyslot_size(hdr, i)) { for(i = 0; i < LUKS_NUMKEYS; ++i) {
log_err(ctx, _("LUKS keyslot %u is invalid.\n"), i); hdr->keyblock[i].active = ntohl(hdr->keyblock[i].active);
r = -EINVAL; hdr->keyblock[i].passwordIterations = ntohl(hdr->keyblock[i].passwordIterations);
} hdr->keyblock[i].keyMaterialOffset = ntohl(hdr->keyblock[i].keyMaterialOffset);
hdr->keyblock[i].stripes = ntohl(hdr->keyblock[i].stripes);
if (LUKS_check_keyslot_size(hdr, i)) {
log_err(ctx, _("LUKS keyslot %u is invalid.\n"), i);
r = -EINVAL;
} }
}
/* Avoid unterminated strings */ /* Avoid unterminated strings */
hdr->cipherName[LUKS_CIPHERNAME_L - 1] = '\0'; hdr->cipherName[LUKS_CIPHERNAME_L - 1] = '\0';
hdr->cipherMode[LUKS_CIPHERMODE_L - 1] = '\0'; hdr->cipherMode[LUKS_CIPHERMODE_L - 1] = '\0';
hdr->uuid[UUID_STRING_L - 1] = '\0'; hdr->uuid[UUID_STRING_L - 1] = '\0';
#if 0
if (r == -EINVAL) { if (repair) {
log_err(ctx, _("Repairing keyslots.\n")); if (r == -EINVAL)
r = _keyslot_repair(device, hdr, ctx); r = _keyslot_repair(device, hdr, ctx);
} else
#endif log_verbose(ctx, _("No known problems detected for LUKS header.\n"));
} }
return r; return r;
@@ -442,7 +467,8 @@ int LUKS_read_phdr_backup(const char *backup_file,
r = -EIO; r = -EIO;
else { else {
LUKS_fix_header_compatible(hdr); LUKS_fix_header_compatible(hdr);
r = _check_and_convert_hdr(backup_file, hdr, require_luks_device, ctx); r = _check_and_convert_hdr(backup_file, hdr,
require_luks_device, 0, ctx);
} }
close(devfd); close(devfd);
@@ -452,11 +478,15 @@ int LUKS_read_phdr_backup(const char *backup_file,
int LUKS_read_phdr(const char *device, int LUKS_read_phdr(const char *device,
struct luks_phdr *hdr, struct luks_phdr *hdr,
int require_luks_device, int require_luks_device,
int repair,
struct crypt_device *ctx) struct crypt_device *ctx)
{ {
ssize_t hdr_size = sizeof(struct luks_phdr); ssize_t hdr_size = sizeof(struct luks_phdr);
int devfd = 0, r = 0; int devfd = 0, r = 0;
if (repair && !require_luks_device)
return -EINVAL;
log_dbg("Reading LUKS header of size %d from device %s", log_dbg("Reading LUKS header of size %d from device %s",
hdr_size, device); hdr_size, device);
@@ -469,7 +499,8 @@ int LUKS_read_phdr(const char *device,
if (read_blockwise(devfd, hdr, hdr_size) < hdr_size) if (read_blockwise(devfd, hdr, hdr_size) < hdr_size)
r = -EIO; r = -EIO;
else else
r = _check_and_convert_hdr(device, hdr, require_luks_device, ctx); r = _check_and_convert_hdr(device, hdr, require_luks_device,
repair, ctx);
close(devfd); close(devfd);
return r; return r;
@@ -521,7 +552,7 @@ int LUKS_write_phdr(const char *device,
/* Re-read header from disk to be sure that in-memory and on-disk data are the same. */ /* Re-read header from disk to be sure that in-memory and on-disk data are the same. */
if (!r) { if (!r) {
r = LUKS_read_phdr(device, hdr, 1, ctx); r = LUKS_read_phdr(device, hdr, 1, 0, ctx);
if (r) if (r)
log_err(ctx, _("Error re-reading LUKS header after update on device %s.\n"), device); log_err(ctx, _("Error re-reading LUKS header after update on device %s.\n"), device);
} }
@@ -894,7 +925,7 @@ int LUKS_del_key(const char *device,
unsigned int startOffset, endOffset, stripesLen; unsigned int startOffset, endOffset, stripesLen;
int r; int r;
r = LUKS_read_phdr(device, hdr, 1, ctx); r = LUKS_read_phdr(device, hdr, 1, 0, ctx);
if (r) if (r)
return r; return r;

View File

@@ -95,6 +95,7 @@ int LUKS_read_phdr(
const char *device, const char *device,
struct luks_phdr *hdr, struct luks_phdr *hdr,
int require_luks_device, int require_luks_device,
int repair,
struct crypt_device *ctx); struct crypt_device *ctx);
int LUKS_read_phdr_backup( int LUKS_read_phdr_backup(

View File

@@ -602,7 +602,7 @@ int crypt_set_data_device(struct crypt_device *cd, const char *device)
return crypt_check_data_device_size(cd); return crypt_check_data_device_size(cd);
} }
static int _crypt_load_luks1(struct crypt_device *cd, int require_header) static int _crypt_load_luks1(struct crypt_device *cd, int require_header, int repair)
{ {
struct luks_phdr hdr; struct luks_phdr hdr;
int r; int r;
@@ -611,7 +611,7 @@ static int _crypt_load_luks1(struct crypt_device *cd, int require_header)
if (r < 0) if (r < 0)
return r; return r;
r = LUKS_read_phdr(mdata_device(cd), &hdr, require_header, cd); r = LUKS_read_phdr(mdata_device(cd), &hdr, require_header, repair, cd);
if (r < 0) if (r < 0)
return r; return r;
@@ -726,7 +726,7 @@ int crypt_init_by_name_and_header(struct crypt_device **cd,
} }
} else if (isLUKS((*cd)->type)) { } else if (isLUKS((*cd)->type)) {
if (mdata_device(*cd)) { if (mdata_device(*cd)) {
r = _crypt_load_luks1(*cd, 0); r = _crypt_load_luks1(*cd, 0, 0);
if (r < 0) { if (r < 0) {
log_dbg("LUKS device header does not match active device."); log_dbg("LUKS device header does not match active device.");
free((*cd)->type); free((*cd)->type);
@@ -972,7 +972,38 @@ int crypt_load(struct crypt_device *cd,
return -EINVAL; return -EINVAL;
} }
r = _crypt_load_luks1(cd, 1); r = _crypt_load_luks1(cd, 1, 0);
if (r < 0)
return r;
/* cd->type and header must be set in context */
r = crypt_check_data_device_size(cd);
if (r < 0) {
free(cd->type);
cd->type = NULL;
}
return r;
}
int crypt_repair(struct crypt_device *cd,
const char *requested_type,
void *params __attribute__((unused)))
{
int r;
log_dbg("Trying to repair %s crypt type from device %s.",
requested_type ?: "any", mdata_device(cd) ?: "(none)");
if (!mdata_device(cd))
return -EINVAL;
if (requested_type && !isLUKS(requested_type))
return -EINVAL;
/* Load with repair */
r = _crypt_load_luks1(cd, 1, 1);
if (r < 0) if (r < 0)
return r; return r;

View File

@@ -175,6 +175,18 @@ form backup file are available after issuing this command.
This command allows restoring header if device do not contain LUKS header This command allows restoring header if device do not contain LUKS header
or if the master key size and data offset in LUKS header on device match the backup file. or if the master key size and data offset in LUKS header on device match the backup file.
.PP .PP
\fIrepair\fR <device>
.IP
Tries to repair (LUKS) device metadata if possible.
This command is useful to fix known benign LUKS metadata header corruptions.
Only basic corruptions of unused keyslot are fixable, any rewrite
of keyslot data or used keyslot or header metadata means lost of device.
\fBWARNING:\fR Always store binary copy of the original header, for
LUKS, \fIrepair\fR will not touch more than 4kB from the start of device
(visible LUKS header).
.PP
For more information about LUKS, see For more information about LUKS, see
\fBhttp://code.google.com/p/cryptsetup/wiki/Specification\fR \fBhttp://code.google.com/p/cryptsetup/wiki/Specification\fR
.SH loop-AES EXTENSION .SH loop-AES EXTENSION

View File

@@ -91,6 +91,7 @@ static int action_luksResume(int arg);
static int action_luksBackup(int arg); static int action_luksBackup(int arg);
static int action_luksRestore(int arg); static int action_luksRestore(int arg);
static int action_loopaesOpen(int arg); static int action_loopaesOpen(int arg);
static int action_luksRepair(int arg);
static struct action_type { static struct action_type {
const char *type; const char *type;
@@ -105,6 +106,7 @@ static struct action_type {
{ "remove", action_remove, 0, 1, 1, N_("<name>"), N_("remove device") }, { "remove", action_remove, 0, 1, 1, N_("<name>"), N_("remove device") },
{ "resize", action_resize, 0, 1, 1, N_("<name>"), N_("resize active device") }, { "resize", action_resize, 0, 1, 1, N_("<name>"), N_("resize active device") },
{ "status", action_status, 0, 1, 0, N_("<name>"), N_("show device status") }, { "status", action_status, 0, 1, 0, N_("<name>"), N_("show device status") },
{ "repair", action_luksRepair, 0, 1, 1, N_("<device>"), N_("try to repair on-disk metadata") },
{ "luksFormat", action_luksFormat, 0, 1, 1, N_("<device> [<new key file>]"), N_("formats a LUKS device") }, { "luksFormat", action_luksFormat, 0, 1, 1, N_("<device> [<new key file>]"), N_("formats a LUKS device") },
{ "luksOpen", action_luksOpen, 0, 2, 1, N_("<device> <name> "), N_("open LUKS device as mapping <name>") }, { "luksOpen", action_luksOpen, 0, 2, 1, N_("<device> <name> "), N_("open LUKS device as mapping <name>") },
{ "luksAddKey", action_luksAddKey, 0, 1, 1, N_("<device> [<new key file>]"), N_("add key to LUKS device") }, { "luksAddKey", action_luksAddKey, 0, 1, 1, N_("<device> [<new key file>]"), N_("add key to LUKS device") },
@@ -517,6 +519,32 @@ fail:
return -EINVAL; return -EINVAL;
} }
static int action_luksRepair(int arg __attribute__((unused)))
{
struct crypt_device *cd = NULL;
int r;
if ((r = crypt_init(&cd, action_argv[0])))
goto out;
/* Currently only LUKS1 allows repair */
crypt_set_log_callback(cd, _quiet_log, NULL);
r = crypt_load(cd, CRYPT_LUKS1, NULL);
crypt_set_log_callback(cd, _log, NULL);
if (r == 0) {
log_verbose( _("No known problems detected for LUKS header.\n"));
goto out;
}
r = _yesDialog(_("Really try to repair LUKS device header?"),
NULL) ? 0 : -EINVAL;
if (r == 0)
r = crypt_repair(cd, CRYPT_LUKS1, NULL);
out:
crypt_free(cd);
return r;
}
static int action_luksFormat(int arg __attribute__((unused))) static int action_luksFormat(int arg __attribute__((unused)))
{ {
int r = -EINVAL, keysize; int r = -EINVAL, keysize;

View File

@@ -495,5 +495,14 @@ $CRYPTSETUP luksSuspend $DEV_NAME --header $HEADER_IMG || fail
echo "key0" | $CRYPTSETUP luksResume $DEV_NAME --header $HEADER_IMG || fail echo "key0" | $CRYPTSETUP luksResume $DEV_NAME --header $HEADER_IMG || fail
$CRYPTSETUP luksClose $DEV_NAME || fail $CRYPTSETUP luksClose $DEV_NAME || fail
prepare "[29] Repair metadata" wipe
$CRYPTSETUP -q luksFormat -i1 $LOOPDEV $KEY1 --key-slot 0 || fail
# second sector overwrite should corrupt keyslot 6+7
dd if=/dev/urandom of=$LOOPDEV bs=512 seek=1 count=1 >/dev/null 2>&1
$CRYPTSETUP luksOpen -d $KEY1 $LOOPDEV $DEV_NAME >/dev/null 2>&1 && fail
$CRYPTSETUP -q repair $LOOPDEV >/dev/null 2>&1 || fail
$CRYPTSETUP luksOpen -d $KEY1 $LOOPDEV $DEV_NAME || fail
$CRYPTSETUP luksClose $DEV_NAME || fail
remove_mapping remove_mapping
exit 0 exit 0