123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505 |
- /* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
- /*
- * Copyright (c) 2016 Red Hat, Inc.
- * Author: Nathaniel McCallum <npmccallum@redhat.com>
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 2.1 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- #include "crc32c.h"
- #include "luksmeta.h"
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <errno.h>
- #include <fcntl.h>
- #include <stdbool.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #define ALIGN(s, up) (((s) + (up ? 4095 : 0)) & ~4095ULL)
- #define LUKS_NSLOTS 8
- #define LM_VERSION 1
- static const uint8_t LM_MAGIC[] = { 'L', 'U', 'K', 'S', 'M', 'E', 'T', 'A' };
- typedef struct __attribute__((packed)) {
- luksmeta_uuid_t uuid;
- uint32_t offset; /* Bytes from the start of the hole */
- uint32_t length; /* Bytes */
- uint32_t crc32c;
- uint32_t _reserved; /* Reserved */
- } lm_slot_t;
- typedef struct __attribute__((packed)) {
- uint8_t magic[sizeof(LM_MAGIC)];
- uint32_t version;
- uint32_t crc32c;
- lm_slot_t slots[LUKS_NSLOTS];
- } lm_t;
- static bool
- uuid_is_zero(const luksmeta_uuid_t uuid)
- {
- for (size_t i = 0; i < sizeof(luksmeta_uuid_t); i++) {
- if (uuid[i] != 0)
- return false;
- }
- return true;
- }
- static inline uint32_t
- checksum(lm_t lm)
- {
- lm.crc32c = 0;
- return crc32c(0, &lm, sizeof(lm_t));
- }
- static inline bool
- overlap(const lm_t *lm, uint32_t start, size_t end)
- {
- for (int i = 0; i < LUKS_NSLOTS; i++) {
- const lm_slot_t *s = &lm->slots[i];
- uint32_t e = s->offset + s->length;
- if (start <= s->offset && s->offset < end)
- return true;
- if (start < e && e <= end)
- return true;
- }
- return false;
- }
- static inline uint32_t
- find_gap(const lm_t *lm, uint32_t length, size_t size)
- {
- size = ALIGN(size, true);
- for (uint32_t off = ALIGN(1, true); off < length; off += ALIGN(1, true)) {
- if (!overlap(lm, off, off + size))
- return off;
- }
- return 0;
- }
- static int
- find_unused_slot(struct crypt_device *cd, const lm_t *lm)
- {
- for (int slot = 0; slot < LUKS_NSLOTS; slot++) {
- if (crypt_keyslot_status(cd, slot) == CRYPT_SLOT_INACTIVE &&
- uuid_is_zero(lm->slots[slot].uuid))
- return slot;
- }
- return -1;
- }
- static inline ssize_t
- readall(int fd, void *data, size_t size)
- {
- uint8_t *tmp = data;
- for (ssize_t r, t = 0; t < (ssize_t) size; t += r) {
- r = read(fd, &tmp[t], size - t);
- if (r < 0 && errno != EAGAIN)
- return -errno;
- if (r == 0)
- return -ENOENT;
- }
- return size;
- }
- static inline ssize_t
- writeall(int fd, const void *buf, size_t size)
- {
- const uint8_t *tmp = buf;
- for (ssize_t r, t = 0; t < (ssize_t) size; t += r) {
- r = write(fd, &tmp[t], size - t);
- if (r < 0) {
- if (errno != EAGAIN)
- return -errno;
- r = 0;
- }
- }
- return size;
- }
- /**
- * Opens the device with the specified flags.
- *
- * The length parameter is set to the amount of space in the gap between the
- * end of the last slot and the start of the encrypted data.
- *
- * The function returns either the file descriptor positioned to the start of
- * the hole or a negative errno.
- */
- static int
- open_hole(struct crypt_device *cd, int flags, uint32_t *length)
- {
- const char *name = NULL;
- const char *type = NULL;
- uint64_t hole = 0;
- uint64_t data = 0;
- int fd = 0;
- int r = 0;
- type = crypt_get_type(cd);
- if (!type || strcmp(CRYPT_LUKS1, type) != 0)
- return -ENOTSUP;
- data = crypt_get_data_offset(cd) * 512;
- if (data < 4096)
- return -ENOSPC;
- for (int slot = 0; slot < LUKS_NSLOTS; slot++) {
- uint64_t off = 0;
- uint64_t len = 0;
- r = crypt_keyslot_area(cd, slot, &off, &len);
- if (r < 0)
- return r;
- if (hole < off + len)
- hole = ALIGN(off + len, true);
- }
- if (hole == 0)
- return -ENOTSUP;
- if (hole >= data)
- return -ENOSPC;
- name = crypt_get_device_name(cd);
- if (!name)
- return -ENOTSUP;
- fd = open(name, flags);
- if (fd < 0)
- return -errno;
- if (lseek(fd, hole, SEEK_SET) == -1) {
- close(fd);
- return -errno;
- }
- *length = ALIGN(data - hole, false);
- return fd;
- }
- static int
- read_header(struct crypt_device *cd, int flags, uint32_t *length, lm_t *lm)
- {
- uint32_t maxlen;
- int fd = -1;
- int r = 0;
- fd = open_hole(cd, flags, length);
- if (fd < 0)
- return fd;
- r = *length >= sizeof(lm_t) ? 0 : -ENOENT;
- if (r < 0)
- goto error;
- r = readall(fd, lm, sizeof(lm_t));
- if (r < 0)
- goto error;
- r = memcmp(LM_MAGIC, lm->magic, sizeof(LM_MAGIC)) == 0 ? 0 : -ENOENT;
- if (r < 0)
- goto error;
- r = lm->version == htobe32(LM_VERSION) ? 0 : -ENOTSUP;
- if (r < 0)
- goto error;
- lm->crc32c = be32toh(lm->crc32c);
- r = checksum(*lm) == lm->crc32c ? 0 : -EINVAL;
- if (r < 0)
- goto error;
- lm->version = be32toh(lm->version);
- maxlen = *length - ALIGN(sizeof(lm_t), true);
- for (int slot = 0; slot < LUKS_NSLOTS; slot++) {
- lm_slot_t *s = &lm->slots[slot];
- s->offset = be32toh(s->offset);
- s->length = be32toh(s->length);
- s->crc32c = be32toh(s->crc32c);
- if (!uuid_is_zero(s->uuid)) {
- r = s->offset > sizeof(lm_t) ? 0 : -EINVAL;
- if (r < 0)
- goto error;
- r = s->length <= maxlen ? 0 : -EINVAL;
- if (r < 0)
- goto error;
- }
- }
- return fd;
- error:
- close(fd);
- return r;
- }
- static int
- write_header(int fd, lm_t lm)
- {
- for (int slot = 0; slot < LUKS_NSLOTS; slot++) {
- lm.slots[slot].offset = htobe32(lm.slots[slot].offset);
- lm.slots[slot].length = htobe32(lm.slots[slot].length);
- lm.slots[slot].crc32c = htobe32(lm.slots[slot].crc32c);
- }
- memcpy(lm.magic, LM_MAGIC, sizeof(LM_MAGIC));
- lm.version = htobe32(LM_VERSION);
- lm.crc32c = htobe32(checksum(lm));
- return writeall(fd, &lm, sizeof(lm));
- }
- int
- luksmeta_test(struct crypt_device *cd)
- {
- int fd = -1;
- fd = read_header(cd, O_RDONLY, &(uint32_t) {0}, &(lm_t) {});
- if (fd >= 0) {
- close(fd);
- return 0;
- }
- return fd;
- }
- int
- luksmeta_nuke(struct crypt_device *cd)
- {
- uint8_t zero[ALIGN(1, true)] = {};
- uint32_t length = 0;
- int fd = -1;
- int r = 0;
- fd = open_hole(cd, O_RDWR | O_SYNC, &length);
- if (fd < 0)
- return fd;
- for (size_t i = 0; r >= 0 && i < length; i += sizeof(zero))
- r = writeall(fd, zero, sizeof(zero));
- close(fd);
- return r < 0 ? r : 0;
- }
- int
- luksmeta_init(struct crypt_device *cd)
- {
- uint32_t length = 0;
- int fd = -1;
- int r = 0;
- r = luksmeta_test(cd);
- if (r == 0)
- return -EALREADY;
- else if (r != -ENOENT && r != -EINVAL)
- return r;
- fd = open_hole(cd, O_RDWR | O_SYNC, &length);
- if (fd < 0)
- return fd;
- if (length < ALIGN(sizeof(lm_t), true)) {
- close(fd);
- return -ENOSPC;
- }
- r = write_header(fd, (lm_t) {});
- close(fd);
- return r > 0 ? 0 : r;
- }
- int
- luksmeta_load(struct crypt_device *cd, int slot,
- luksmeta_uuid_t uuid, void *buf, size_t size)
- {
- uint32_t length = 0;
- lm_slot_t *s = NULL;
- lm_t lm = {};
- int fd = -1;
- int r = 0;
- if (slot < 0 || slot >= LUKS_NSLOTS)
- return -EBADSLT;
- s = &lm.slots[slot];
- fd = read_header(cd, O_RDONLY, &length, &lm);
- if (fd < 0)
- return fd;
- r = uuid_is_zero(s->uuid) ? -ENODATA : 0;
- if (r < 0)
- goto error;
- if (buf) {
- r = size >= s->length ? 0 : -E2BIG;
- if (r < 0)
- goto error;
- r = lseek(fd, s->offset - sizeof(lm), SEEK_CUR) == -1 ? -errno : 0;
- if (r < 0)
- goto error;
- r = readall(fd, buf, s->length);
- if (r < 0)
- goto error;
- r = crc32c(0, buf, s->length) == s->crc32c ? 0 : -EINVAL;
- if (r < 0)
- goto error;
- }
- memcpy(uuid, s->uuid, sizeof(luksmeta_uuid_t));
- close(fd);
- return s->length;
- error:
- close(fd);
- return r;
- }
- int
- luksmeta_save(struct crypt_device *cd, int slot,
- const luksmeta_uuid_t uuid, const void *buf, size_t size)
- {
- uint32_t length = 0;
- lm_slot_t *s = NULL;
- lm_t lm = {};
- int fd = -1;
- int r = 0;
- off_t off;
- if (uuid_is_zero(uuid))
- return -EKEYREJECTED;
- fd = read_header(cd, O_RDWR | O_SYNC, &length, &lm);
- if (fd < 0)
- return fd;
- if (slot == CRYPT_ANY_SLOT)
- slot = find_unused_slot(cd, &lm);
- r = slot >= 0 && slot < LUKS_NSLOTS ? 0 : -EBADSLT;
- if (r < 0)
- goto error;
- s = &lm.slots[slot];
- r = uuid_is_zero(s->uuid) ? 0 : -EALREADY;
- if (r < 0)
- goto error;
- s->offset = find_gap(&lm, length, size);
- r = s->offset >= ALIGN(sizeof(lm), true) ? 0 : -ENOSPC;
- if (r < 0)
- goto error;
- memcpy(s->uuid, uuid, sizeof(luksmeta_uuid_t));
- s->length = size;
- s->crc32c = crc32c(0, buf, size);
- off = s->offset - sizeof(lm);
- r = lseek(fd, off, SEEK_CUR) == -1 ? -errno : 0;
- if (r < 0)
- goto error;
- r = writeall(fd, buf, size);
- if (r < 0)
- goto error;
- off = s->offset + s->length;
- r = lseek(fd, -off, SEEK_CUR) == -1 ? -errno : 0;
- if (r < 0)
- goto error;
- r = write_header(fd, lm);
- error:
- close(fd);
- return r < 0 ? r : slot;
- }
- int
- luksmeta_wipe(struct crypt_device *cd, int slot, const luksmeta_uuid_t uuid)
- {
- uint8_t *zero = NULL;
- uint32_t length = 0;
- lm_slot_t *s = NULL;
- lm_t lm = {};
- int fd = -1;
- int r = 0;
- off_t off;
- if (slot < 0 || slot >= LUKS_NSLOTS)
- return -EBADSLT;
- s = &lm.slots[slot];
- fd = read_header(cd, O_RDWR | O_SYNC, &length, &lm);
- if (fd < 0)
- return fd;
- r = uuid_is_zero(s->uuid) ? -EALREADY : 0;
- if (r < 0)
- goto error;
- if (uuid && memcmp(uuid, s->uuid, sizeof(luksmeta_uuid_t)) != 0) {
- r = -EKEYREJECTED;
- goto error;
- }
- off = s->offset - sizeof(lm_t);
- r = lseek(fd, off, SEEK_CUR) == -1 ? -errno : 0;
- if (r < 0)
- goto error;
- r = (zero = calloc(1, s->length)) ? 0 : -errno;
- if (r < 0)
- goto error;
- r = writeall(fd, zero, s->length);
- free(zero);
- if (r < 0)
- goto error;
- off = s->offset + s->length;
- r = lseek(fd, -off, SEEK_CUR) == -1 ? -errno : 0;
- if (r < 0)
- goto error;
- memset(s, 0, sizeof(lm_slot_t));
- r = write_header(fd, lm);
- error:
- close(fd);
- return r < 0 ? r : 0;
- }
|