diff --git a/ChangeLog b/ChangeLog index bacd7396..0b057698 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,6 @@ 2012-03-16 Milan Broz * 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 * Unify password verification option. diff --git a/lib/libcryptsetup.h b/lib/libcryptsetup.h index 2c485039..f73c8498 100644 --- a/lib/libcryptsetup.h +++ b/lib/libcryptsetup.h @@ -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 * diff --git a/lib/libcryptsetup.sym b/lib/libcryptsetup.sym index fcbd4dd6..88feb226 100644 --- a/lib/libcryptsetup.sym +++ b/lib/libcryptsetup.sym @@ -17,6 +17,7 @@ CRYPTSETUP_1.0 { crypt_memory_lock; crypt_format; crypt_load; + crypt_repair; crypt_resize; crypt_suspend; crypt_resume_by_passphrase; diff --git a/lib/luks1/keymanage.c b/lib/luks1/keymanage.c index 3652191d..2b573291 100644 --- a/lib/luks1/keymanage.c +++ b/lib/luks1/keymanage.c @@ -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,39 +388,44 @@ 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 { - hdr->payloadOffset = ntohl(hdr->payloadOffset); - hdr->keyBytes = ntohl(hdr->keyBytes); - hdr->mkDigestIterations = ntohl(hdr->mkDigestIterations); + return -EINVAL; + } - for(i = 0; i < LUKS_NUMKEYS; ++i) { - hdr->keyblock[i].active = ntohl(hdr->keyblock[i].active); - 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; - } + /* Header detected */ + hdr->payloadOffset = ntohl(hdr->payloadOffset); + hdr->keyBytes = ntohl(hdr->keyBytes); + hdr->mkDigestIterations = ntohl(hdr->mkDigestIterations); + + for(i = 0; i < LUKS_NUMKEYS; ++i) { + hdr->keyblock[i].active = ntohl(hdr->keyblock[i].active); + 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 */ - 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")); + /* Avoid unterminated strings */ + hdr->cipherName[LUKS_CIPHERNAME_L - 1] = '\0'; + hdr->cipherMode[LUKS_CIPHERMODE_L - 1] = '\0'; + hdr->uuid[UUID_STRING_L - 1] = '\0'; + + 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; diff --git a/lib/luks1/luks.h b/lib/luks1/luks.h index ec52485c..6a2caaf4 100644 --- a/lib/luks1/luks.h +++ b/lib/luks1/luks.h @@ -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( diff --git a/lib/setup.c b/lib/setup.c index 23089255..4bb06314 100644 --- a/lib/setup.c +++ b/lib/setup.c @@ -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; diff --git a/man/cryptsetup.8 b/man/cryptsetup.8 index 88d781fe..b9298a54 100644 --- a/man/cryptsetup.8 +++ b/man/cryptsetup.8 @@ -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 +.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 diff --git a/src/cryptsetup.c b/src/cryptsetup.c index 62a534ef..a56fb289 100644 --- a/src/cryptsetup.c +++ b/src/cryptsetup.c @@ -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_(""), N_("remove device") }, { "resize", action_resize, 0, 1, 1, N_(""), N_("resize active device") }, { "status", action_status, 0, 1, 0, N_(""), N_("show device status") }, + { "repair", action_luksRepair, 0, 1, 1, N_(""), N_("try to repair on-disk metadata") }, { "luksFormat", action_luksFormat, 0, 1, 1, N_(" []"), N_("formats a LUKS device") }, { "luksOpen", action_luksOpen, 0, 2, 1, N_(" "), N_("open LUKS device as mapping ") }, { "luksAddKey", action_luksAddKey, 0, 1, 1, N_(" []"), 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; diff --git a/tests/compat-test b/tests/compat-test index 49830564..17ef552d 100755 --- a/tests/compat-test +++ b/tests/compat-test @@ -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