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>
* 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>
* Unify password verification option.

View File

@@ -391,6 +391,20 @@ int crypt_load(struct crypt_device *cd,
const char *requested_type,
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
*

View File

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

View File

@@ -149,7 +149,7 @@ int LUKS_hdr_backup(
return -EINVAL;
}
r = LUKS_read_phdr(device, hdr, 1, ctx);
r = LUKS_read_phdr(device, hdr, 1, 0, ctx);
if (r)
return r;
@@ -245,7 +245,7 @@ int LUKS_hdr_restore(
}
close(devfd);
r = LUKS_read_phdr(device, hdr, 0, ctx);
r = LUKS_read_phdr(device, hdr, 0, 0, ctx);
if (r == 0) {
log_dbg("Device %s already contains LUKS header, checking UUID and offset.", device);
if(hdr->payloadOffset != hdr_file.payloadOffset ||
@@ -288,7 +288,7 @@ int LUKS_hdr_restore(
close(devfd);
/* Be sure to reload new data */
r = LUKS_read_phdr(device, hdr, 1, ctx);
r = LUKS_read_phdr(device, hdr, 1, 0, ctx);
out:
if (devfd != -1)
close(devfd);
@@ -296,23 +296,35 @@ out:
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)
{
struct luks_phdr temp_phdr;
const unsigned char *sector = (const unsigned char*)phdr;
struct volume_key *vk;
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);
// FIXME check cipher, cipher_mode, hash, uuid, payloadOffset
LUKS_generate_phdr(&temp_phdr, vk, phdr->cipherName, phdr->cipherMode,
log_verbose(ctx, _("Repairing keyslots.\n"));
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->payloadOffset, 0,
1, &PBKDF2_per_sec,
"/dev/null", ctx);
if (r < 0) {
log_err(ctx, _("Repair failed."));
goto out;
}
for(i = 0; i < LUKS_NUMKEYS; ++i) {
bad = 0;
@@ -332,7 +344,7 @@ static int _keyslot_repair(const char *device, struct luks_phdr *phdr, struct cr
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) {
/* Known case - MSDOS partition table signature */
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;
}
}
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);
memset(&temp_phdr, 0, sizeof(temp_phdr));
return LUKS_write_phdr(device, phdr, ctx);
return r;
}
static int _check_and_convert_hdr(const char *device,
struct luks_phdr *hdr,
int require_luks_device,
int repair,
struct crypt_device *ctx)
{
int r = 0;
@@ -368,14 +388,19 @@ static int _check_and_convert_hdr(const char *device,
log_dbg("LUKS header not detected.");
if (require_luks_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 */
log_err(ctx, _("Unsupported LUKS version %d.\n"), hdr->version);
r = -EINVAL;
} else if (PBKDF2_HMAC_ready(hdr->hashSpec) < 0) {
return -EINVAL;
}
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);
r = -EINVAL;
} else {
return -EINVAL;
}
/* Header detected */
hdr->payloadOffset = ntohl(hdr->payloadOffset);
hdr->keyBytes = ntohl(hdr->keyBytes);
hdr->mkDigestIterations = ntohl(hdr->mkDigestIterations);
@@ -395,12 +420,12 @@ static int _check_and_convert_hdr(const char *device,
hdr->cipherName[LUKS_CIPHERNAME_L - 1] = '\0';
hdr->cipherMode[LUKS_CIPHERMODE_L - 1] = '\0';
hdr->uuid[UUID_STRING_L - 1] = '\0';
#if 0
if (r == -EINVAL) {
log_err(ctx, _("Repairing keyslots.\n"));
if (repair) {
if (r == -EINVAL)
r = _keyslot_repair(device, hdr, ctx);
}
#endif
else
log_verbose(ctx, _("No known problems detected for LUKS header.\n"));
}
return r;
@@ -442,7 +467,8 @@ int LUKS_read_phdr_backup(const char *backup_file,
r = -EIO;
else {
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);
@@ -452,11 +478,15 @@ int LUKS_read_phdr_backup(const char *backup_file,
int LUKS_read_phdr(const char *device,
struct luks_phdr *hdr,
int require_luks_device,
int repair,
struct crypt_device *ctx)
{
ssize_t hdr_size = sizeof(struct luks_phdr);
int devfd = 0, r = 0;
if (repair && !require_luks_device)
return -EINVAL;
log_dbg("Reading LUKS header of size %d from device %s",
hdr_size, device);
@@ -469,7 +499,8 @@ int LUKS_read_phdr(const char *device,
if (read_blockwise(devfd, hdr, hdr_size) < hdr_size)
r = -EIO;
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);
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. */
if (!r) {
r = LUKS_read_phdr(device, hdr, 1, ctx);
r = LUKS_read_phdr(device, hdr, 1, 0, ctx);
if (r)
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;
int r;
r = LUKS_read_phdr(device, hdr, 1, ctx);
r = LUKS_read_phdr(device, hdr, 1, 0, ctx);
if (r)
return r;

View File

@@ -95,6 +95,7 @@ int LUKS_read_phdr(
const char *device,
struct luks_phdr *hdr,
int require_luks_device,
int repair,
struct crypt_device *ctx);
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);
}
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;
int r;
@@ -611,7 +611,7 @@ static int _crypt_load_luks1(struct crypt_device *cd, int require_header)
if (r < 0)
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)
return r;
@@ -726,7 +726,7 @@ int crypt_init_by_name_and_header(struct crypt_device **cd,
}
} else if (isLUKS((*cd)->type)) {
if (mdata_device(*cd)) {
r = _crypt_load_luks1(*cd, 0);
r = _crypt_load_luks1(*cd, 0, 0);
if (r < 0) {
log_dbg("LUKS device header does not match active device.");
free((*cd)->type);
@@ -972,7 +972,38 @@ int crypt_load(struct crypt_device *cd,
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)
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
or if the master key size and data offset in LUKS header on device match the backup file.
.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
\fBhttp://code.google.com/p/cryptsetup/wiki/Specification\fR
.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_luksRestore(int arg);
static int action_loopaesOpen(int arg);
static int action_luksRepair(int arg);
static struct action_type {
const char *type;
@@ -105,6 +106,7 @@ static struct action_type {
{ "remove", action_remove, 0, 1, 1, N_("<name>"), N_("remove 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") },
{ "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") },
{ "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") },
@@ -517,6 +519,32 @@ fail:
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)))
{
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
$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
exit 0