Skip to contentSkip to conversation

Data integrity protection with dm-integrity and dm-crypt

RFC PATCH New Milan Broz 2 participants 14 messages Jan 4, 2017 – Mar 16, 2017

Subsystems (3)
lore
Share & tools
lore

Milan Broz

author
#0 lore raw

cover Data integrity protection with dm-integrity and dm-crypt

Hello,

following patches are the first iteration of our work on providing data integrity protection on the block device level.

This covering mail is very simple introduction to motivation of this work, more rigorous description will come in the form of some paper later.

Our main goal is to provide an option to implement strong data integrity protection on the low block device level, without need of any specialized hardware - just as an extension of device-mapper infrastructure.

The solution is meant to be used over a standard common-off-the-shelf device. N.B. we are not replacing nor disputing a need to implement some data integrity protection on the higher level. But the state of integrity protection on higher level is in sad state in Linux in general and we believe that this target can provide at least something to the rescue :)

My special focus (and my academic motivation) is to provide cryptographically strong data protection in combination with full disk encryption algorithms.

In principle, so-called authenticated encryption provides guaranties that data were not tampered with, so only authorised user can decrypt, change or verify data integrity.
N.B. Current full disk encryption algorithms provide only confidentiality, they does NOT provide any data integrity protection (because of its length-preserving nature).
Corrupted data cannot be easily detected (except detection that decrypted data seems to be garbage).

I. dm-integrity - a "simulated" data integrity profile
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Any integrity protection requires additional data to store data protection tags. Because we are operating on the sector level, we need an additional per-sector metadata for any integrity (or other) tags.

Attached patches include new dm-integrity target that provides basic logic to store additional metadata per-sector.

On top of it we can provide data integrity protection, either inside dm-integrity itself or in stacked device like dm-crypt.

The basic idea of dm-integrity is to "simulate" DIF/IP profile over a normal block device and store a per-sector metadata in interleaved metadata sectors. It using existing implementation of integrity profile metadata already present in kernel, just registering own configurable profile.

To provide atomic update of data + metadata sectors, dm-integrity implements also optional data journal.

The dm-integrity target can operate in two modes:
* it can use metadata itself and calculate non-cryptographic data integrity (for example CRC or hash-based checksum)

* it can receive bio structure with attached integrity fields and just provide handler for these metadata (read/store them to metadata sectors). It is up to higher target how the metadata are used.

The second mode is used for the dm-crypt authenticated encryption extension.

II. dm-crypt and authenticated encryption
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The second part adds possibility to use Authenticated Encryption with Additional Data (AEAD) algorithms for dm-crypt.

These algorithms can encrypt data and also produce additional authentication tag that provides cryptographically sound integrity protection.
There can be also additional data (that are only authenticated but not encrypted), in disk encryption is it for example sector number (so the sector fails integrity validation if ciphertext sector is relocated).

This authentication tag is then stored into metadata space provided by underlying dm-integrity target. On top of this we can also store another metadata like randomly generated, Initialization Vector (IV). (This could increase security of disk-encryption model. Basically if IV can be randomly regenerated on every write, ciphertext sector will change pseudo-randomly on every write, even with the same written data. This is the best possible achievable security scenario for disk-encryption.)

In principle we can use any authenticated mode provided by kernel crypto API (but not all authenticated mode are secure in this environment, this applies mainly for standard GCM mode).

We can also compose AEAD mode using existing disk encryption mode (XTS) and combine it with keyed hash (HMAC) like HMAC(SHA256).

For illustration, the dm-crypt authenticated encryption uses following format:

 |-ADDITIONAL DATA-|------ DATA -------|-- AUTH TAG --| 
 | (authenticated) | (auth+encryption) |              |
 | sector_LE |  IV |  sector in/out    |  tag in/out  |

Sector_LE is sector number in little-endian, IV is authenticated as well.

The logic is in principle compatible with IEEE 1619.1 standard recommendation.

~~~~~~~~~~~~~~~~~~~

For more information please see individual patches.

The price for this solution is obviously storage space and performance. Our solution can be quite easily extended of other information stored in metadata, for example of Forward Error Correction (FEC) codes.
(I plan to experiment with this later).

I will try to provide some testing examples soon (we have already experimental support in LUKS userspace based on experimental LUKS2 format).

(I hope I can show more on DevConf.cz talk later this January in Brno, I hope we can discuss it there as well.)

Any constructive feedback welcome!

Thanks,
Milan (and Mikulas)

Mikulas Patocka (2):
Add sector start offset to dm-bufio interface.
Add the dm-integrity target

Milan Broz (2):
dm-table: Add flag to allow own target handling of integrity metadata. Add cryptographic data integrity protection (authenticated encryption)
to dm-crypt.

 Documentation/device-mapper/dm-crypt.txt     |   16 +
 Documentation/device-mapper/dm-integrity.txt |  189 ++
 drivers/md/Kconfig                           |   10 +
 drivers/md/Makefile                          |    1 +
 drivers/md/dm-bufio.c                        |   51 +-
 drivers/md/dm-bufio.h                        |    7 +
 drivers/md/dm-crypt.c                        |  857 ++++++--
 drivers/md/dm-integrity.c                    | 3031 ++++++++++++++++++++++++++
 drivers/md/dm-table.c                        |   11 +
 include/linux/device-mapper.h                |    6 +
 10 files changed, 4029 insertions(+), 150 deletions(-)
 create mode 100644 Documentation/device-mapper/dm-integrity.txt
 create mode 100644 drivers/md/dm-integrity.c
Signature · 1 line
2.11.0
author
#1 lore raw

RFC PATCH 1/4 dm-table: Add flag to allow own target handling of integrity metadata.

This patch adds DM_TARGET_INTEGRITY flag that specifies that bio integrity metadata is not inherited but implemented in target itself.

---
 drivers/md/dm-table.c         | 11 +++++++++++
 include/linux/device-mapper.h |  6 ++++++
 2 files changed, 17 insertions(+)
drivers/md/dm-table.c +11 -0
index 0a427de23ed2..95094c133d6c 100644
@@ -47,6 +47,7 @@ struct dm_table {
 	bool integrity_supported:1;
 	bool singleton:1;
 	bool all_blk_mq:1;
+	unsigned integrity_added:1;
 
 	/*
 	 * Indicates the rw permissions for the new logical
@@ -725,6 +726,9 @@ int dm_table_add_target(struct dm_table *t, const char *type,
 		t->immutable_target_type = tgt->type;
 	}
 
+	if (dm_target_has_integrity(tgt->type))
+		t->integrity_added = 1;
+
 	tgt->table = t;
 	tgt->begin = start;
 	tgt->len = len;
@@ -1168,6 +1172,10 @@ static int dm_table_register_integrity(struct dm_table *t)
 	struct mapped_device *md = t->md;
 	struct gendisk *template_disk = NULL;
 
+	/* If target handles integrity itself do not register it here. */
+	if (t->integrity_added)
+		return 0;
+
 	template_disk = dm_table_get_integrity_disk(t);
 	if (!template_disk)
 		return 0;
@@ -1394,6 +1402,9 @@ static void dm_table_verify_integrity(struct dm_table *t)
 {
 	struct gendisk *template_disk = NULL;
 
+	if (t->integrity_added)
+		return;
+
 	if (t->integrity_supported) {
 		/*
 		 * Verify that the original integrity profile
include/linux/device-mapper.h +6 -0
index ef7962e84444..b508e8169133 100644
@@ -224,6 +224,12 @@ struct target_type {
  */
 typedef unsigned (*dm_num_write_bios_fn) (struct dm_target *ti, struct bio *bio);
 
+/*
+ * A target implements own bio data integrity.
+ */
+#define DM_TARGET_INTEGRITY		0x00000010
+#define dm_target_has_integrity(type)	((type)->features & DM_TARGET_INTEGRITY)
+
 struct dm_target {
 	struct dm_table *table;
 	struct target_type *type;
Signature · 1 line
2.11.0
author
#2 lore raw

RFC PATCH 2/4 Add sector start offset to dm-bufio interface.

In reply to Milan Broz

From: Mikulas Patocka <[email protected]>

This patch adds possibility to set sector offset for dm-bufio client.

---
 drivers/md/dm-bufio.c | 51 ++++++++++++++++++++++++++++++++-------------------
 drivers/md/dm-bufio.h |  7 +++++++
 2 files changed, 39 insertions(+), 19 deletions(-)
drivers/md/dm-bufio.c +32 -19
index 84d2f0e4c754..0105f9196ab3 100644
@@ -109,6 +109,8 @@ struct dm_bufio_client {
 	struct rb_root buffer_tree;
 	wait_queue_head_t free_buffer_wait;
 
+	sector_t start;
+
 	int async_write_error;
 
 	struct list_head client_list;
@@ -556,8 +558,8 @@ static void dmio_complete(unsigned long error, void *context)
 	b->bio.bi_end_io(&b->bio);
 }
 
-static void use_dmio(struct dm_buffer *b, int rw, sector_t block,
-		     bio_end_io_t *end_io)
+static void use_dmio(struct dm_buffer *b, int rw, sector_t sector,
+		     unsigned n_sectors, bio_end_io_t *end_io)
 {
 	int r;
 	struct dm_io_request io_req = {
@@ -569,8 +571,8 @@ static void use_dmio(struct dm_buffer *b, int rw, sector_t block,
 	};
 	struct dm_io_region region = {
 		.bdev = b->c->bdev,
-		.sector = block << b->c->sectors_per_block_bits,
-		.count = b->c->block_size >> SECTOR_SHIFT,
+		.sector = sector,
+		.count = n_sectors,
 	};
 
 	if (b->data_mode != DATA_MODE_VMALLOC) {
@@ -605,14 +607,14 @@ static void inline_endio(struct bio *bio)
 	end_fn(bio);
 }
 
-static void use_inline_bio(struct dm_buffer *b, int rw, sector_t block,
-			   bio_end_io_t *end_io)
+static void use_inline_bio(struct dm_buffer *b, int rw, sector_t sector,
+			   unsigned n_sectors, bio_end_io_t *end_io)
 {
 	char *ptr;
 	int len;
 
 	bio_init(&b->bio, b->bio_vec, DM_BUFIO_INLINE_VECS);
-	b->bio.bi_iter.bi_sector = block << b->c->sectors_per_block_bits;
+	b->bio.bi_iter.bi_sector = sector;
 	b->bio.bi_bdev = b->c->bdev;
 	b->bio.bi_end_io = inline_endio;
 	/*
@@ -627,7 +629,7 @@ static void use_inline_bio(struct dm_buffer *b, int rw, sector_t block,
 	 * If len < PAGE_SIZE the buffer doesn't cross page boundary.
 	 */
 	ptr = b->data;
-	len = b->c->block_size;
+	len = n_sectors << SECTOR_SHIFT;
 
 	if (len >= PAGE_SIZE)
 		BUG_ON((unsigned long)ptr & (PAGE_SIZE - 1));
@@ -639,7 +641,7 @@ static void use_inline_bio(struct dm_buffer *b, int rw, sector_t block,
 				  len < PAGE_SIZE ? len : PAGE_SIZE,
 				  offset_in_page(ptr))) {
 			BUG_ON(b->c->block_size <= PAGE_SIZE);
-			use_dmio(b, rw, block, end_io);
+			use_dmio(b, rw, sector, n_sectors, end_io);
 			return;
 		}
 
@@ -650,17 +652,22 @@ static void use_inline_bio(struct dm_buffer *b, int rw, sector_t block,
 	submit_bio(&b->bio);
 }
 
-static void submit_io(struct dm_buffer *b, int rw, sector_t block,
-		      bio_end_io_t *end_io)
+static void submit_io(struct dm_buffer *b, int rw, bio_end_io_t *end_io)
 {
+	unsigned n_sectors;
+	sector_t sector;
+
 	if (rw == WRITE && b->c->write_callback)
 		b->c->write_callback(b);
 
-	if (b->c->block_size <= DM_BUFIO_INLINE_VECS * PAGE_SIZE &&
+	sector = (b->block << b->c->sectors_per_block_bits) + b->c->start;
+	n_sectors = 1 << b->c->sectors_per_block_bits;
+
+	if (n_sectors <= ((DM_BUFIO_INLINE_VECS * PAGE_SIZE) >> SECTOR_SHIFT) &&
 	    b->data_mode != DATA_MODE_VMALLOC)
-		use_inline_bio(b, rw, block, end_io);
+		use_inline_bio(b, rw, sector, n_sectors, end_io);
 	else
-		use_dmio(b, rw, block, end_io);
+		use_dmio(b, rw, sector, n_sectors, end_io);
 }
 
 /*----------------------------------------------------------------
@@ -712,7 +719,7 @@ static void __write_dirty_buffer(struct dm_buffer *b,
 	wait_on_bit_lock_io(&b->state, B_WRITING, TASK_UNINTERRUPTIBLE);
 
 	if (!write_list)
-		submit_io(b, WRITE, b->block, write_endio);
+		submit_io(b, WRITE, write_endio);
 	else
 		list_add_tail(&b->write_list, write_list);
 }
@@ -725,7 +732,7 @@ static void __flush_write_list(struct list_head *write_list)
 		struct dm_buffer *b =
 			list_entry(write_list->next, struct dm_buffer, write_list);
 		list_del(&b->write_list);
-		submit_io(b, WRITE, b->block, write_endio);
+		submit_io(b, WRITE, write_endio);
 		cond_resched();
 	}
 	blk_finish_plug(&plug);
@@ -1093,7 +1100,7 @@ static void *new_read(struct dm_bufio_client *c, sector_t block,
 		return NULL;
 
 	if (need_submit)
-		submit_io(b, READ, b->block, read_endio);
+		submit_io(b, READ, read_endio);
 
 	wait_on_bit_io(&b->state, B_READING, TASK_UNINTERRUPTIBLE);
 
@@ -1163,7 +1170,7 @@ void dm_bufio_prefetch(struct dm_bufio_client *c,
 			dm_bufio_unlock(c);
 
 			if (need_submit)
-				submit_io(b, READ, b->block, read_endio);
+				submit_io(b, READ, read_endio);
 			dm_bufio_release(b);
 
 			cond_resched();
@@ -1404,7 +1411,7 @@ void dm_bufio_release_move(struct dm_buffer *b, sector_t new_block)
 		old_block = b->block;
 		__unlink_buffer(b);
 		__link_buffer(b, new_block, b->list_mode);
-		submit_io(b, WRITE, new_block, write_endio);
+		submit_io(b, WRITE, write_endio);
 		wait_on_bit_io(&b->state, B_WRITING,
 			       TASK_UNINTERRUPTIBLE);
 		__unlink_buffer(b);
@@ -1761,6 +1768,12 @@ void dm_bufio_client_destroy(struct dm_bufio_client *c)
 }
 EXPORT_SYMBOL_GPL(dm_bufio_client_destroy);
 
+void dm_bufio_set_sector_offset(struct dm_bufio_client *c, sector_t start)
+{
+	c->start = start;
+}
+EXPORT_SYMBOL_GPL(dm_bufio_set_sector_offset);
+
 static unsigned get_max_age_hz(void)
 {
 	unsigned max_age = ACCESS_ONCE(dm_bufio_max_age);
drivers/md/dm-bufio.h +7 -0
index c096779a7292..b6d8f53ec15b 100644
@@ -32,6 +32,13 @@ dm_bufio_client_create(struct block_device *bdev, unsigned block_size,
 void dm_bufio_client_destroy(struct dm_bufio_client *c);
 
 /*
+ * Set the sector range.
+ * When this function is called, there must be no I/O in progress on the bufio
+ * client.
+ */
+void dm_bufio_set_sector_offset(struct dm_bufio_client *c, sector_t start);
+
+/*
  * WARNING: to avoid deadlocks, these conditions are observed:
  *
  * - At most one thread can hold at most "reserved_buffers" simultaneously.
Signature · 1 line
2.11.0
author
#3 lore raw

RFC PATCH 3/4 Add the dm-integrity target

In reply to Milan Broz

From: Mikulas Patocka <[email protected]>

The dm-integrity target emulates a block device that has additional per-sector tags that can be used for storing integrity information.

A general problem with storing integrity tags with every sector is that writing the sector and the integrity tag must be atomic - i.e. in case of crash, either both sector and integrity tag or none of them is written.

To guarantee write atomicity, the dm-integrity target uses journal, it writes sector data and integrity tags into a journal, commits the journal and then copies the data and integrity tags to their respective location.

The dm-integrity target can be used with the dm-crypt target - in this situation the dm-crypt target creates the integrity data and passes them to the dm-integrity target via bio_integrity_payload attached to the bio. In this mode, the dm-crypt and dm-integrity targets provide authenticated disk encryption - if the attacker modifies the encrypted device, an I/O error is returned instead of random data.

The dm-integrity target can also be used as a standalone target, in this mode it calculates and verifies the integrity tag internally. In this mode, the dm-integrity target can be used to detect silent data corruption on the disk or in the I/O path.

---
 Documentation/device-mapper/dm-integrity.txt |  189 ++
 drivers/md/Kconfig                           |   10 +
 drivers/md/Makefile                          |    1 +
 drivers/md/dm-integrity.c                    | 3031 ++++++++++++++++++++++++++
 4 files changed, 3231 insertions(+)
 create mode 100644 Documentation/device-mapper/dm-integrity.txt
 create mode 100644 drivers/md/dm-integrity.c
Documentation/device-mapper/dm-integrity.txt new file +189 -0
new file mode 100644
index 000000000000..2406f56501dc
@@ -0,0 +1,189 @@
+The dm-integrity target emulates a block device that has additional
+per-sector tags that can be used for storing integrity information.
+
+A general problem with storing integrity tags with every sector is that
+writing the sector and the integrity tag must be atomic - i.e. in case of
+crash, either both sector and integrity tag or none of them is written.
+
+To guarantee write atomicity, the dm-integrity target uses journal, it
+writes sector data and integrity tags into a journal, commits the journal
+and then copies the data and integrity tags to their respective location.
+
+The dm-integrity target can be used with the dm-crypt target - in this
+situation the dm-crypt target creates the integrity data and passes them
+to the dm-integrity target via bio_integrity_payload attached to the bio.
+In this mode, the dm-crypt and dm-integrity targets provide authenticated
+disk encryption - if the attacker modifies the encrypted device, an I/O
+error is returned instead of random data.
+
+The dm-integrity target can also be used as a standalone target, in this
+mode it calculates and verifies the integrity tag internally. In this
+mode, the dm-integrity target can be used to detect silent data
+corruption on the disk or in the I/O path.
+
+
+When loading the target for the first time, the kernel driver will format
+the device. But it will only format the device if the superblock contains
+zeroes. If the superblock is neither valid nor zeroed, the dm-integrity
+target can't be loaded.
+
+To use the target for the first time:
+1. overwrite the superblock with zeroes
+2. load the dm-integrity target with one-sector size, the kernel driver
+	will format the device
+3. unload the dm-integrity target
+4. read the "provided_data_sectors" value from the superblock
+5. load the dm-integrity target with the the target size
+	"provided_data_sectors"
+6. if you want to use dm-integrity with dm-crypt, load the dm-crypt target
+	with the size "provided_data_sectors"
+
+
+Target arguments:
+
+1. the underlying block device
+
+2. the number of reserved sector at the beginning of the device - the
+	dm-integrity won't read of write these sectors
+
+3. the size of the integrity tag (if "-" is used, the size is taken from
+	the internal-hash algorithm)
+
+4. mode:
+	D - direct writes (without journal) - in this mode, journaling is
+		not used and data sectors and integrity tags are written
+		separately. In case of crash, it is possible that the data
+		and integrity tag doesn't match.
+	J - journaled writes - data and integrity tags are written to the
+		journal and atomicity is guaranteed. In case of crash,
+		either both data and tag or none of them are written. The
+		journaled mode degrades write throughput twice because the
+		data have to be written twice.
+
+5. the number of additional arguments
+
+Additional arguments:
+
+journal-sectors:number
+	The size of journal, this argument is used only if formatting the
+	device. If the device is already formatted, the value from the
+	superblock is used.
+
+interleave-sectors:number
+	The number of interleaved sectors. This values is rounded down to
+	a power of two. If the device is already formatted, the value from
+	the superblock is used.
+
+buffer-sectors:number
+	The number of sectors in one buffer. The value is rounded down to
+	a power of two.
+
+	The tag area is accessed using buffers, the buffer size is
+	configurable. The large buffer size means that the I/O size will
+	be larger, but there could be less I/Os issued.
+
+journal-watermark:number
+	The journal watermark in percents. When the size of the journal
+	exceeds this watermark, the thread that flushes the journal will
+	be started.
+
+commit-time:number
+	Commit time in milliseconds. When this time passes, the journal is
+	written. The journal is also written immediatelly if the FLUSH
+	request is received.
+
+internal-hash:algorithm(:key)	(the key is optional)
+	Use internal hash or crc.
+	When this argument is used, the dm-integrity target won't accept
+	integrity tags from the upper target, but it will automatically
+	generate and verify the integrity tags.
+
+	You can use a crc algorithm (such as crc32), then integrity target
+	will protect the data against accidental corruption.
+	You can also use a hmac algorithm (for example
+	"hmac(sha256):0123456789abcdef"), in this mode it will provide
+	cryptographic authentication of the data without encryption.
+
+	When this argument is not used, the integrity tags are accepted
+	from an upper layer target, such as dm-crypt. The upper layer
+	target should check the validity of the integrity tags.
+
+journal-crypt:algorithm(:key)	(the key is optional)
+	Encrypt the journal using given algorithm to make sure that the
+	attacker can't read the journal. You can use a block cipher here
+	(such as "cbc(aes)") or a stream cipher (for example "chacha20",
+	"salsa20", "ctr(aes)" or "ecb(arc4)").
+
+	The journal contains history of last writes to the block device,
+	an attacker reading the journal could see the last sector nubmers
+	that were written. From the sector numbers, the attacker can infer
+	the size of files that were written. To protect against this
+	situation, you can encrypt the journal.
+
+journal-mac:algorithm(:key)	(the key is optional)
+	Protect sector numbers in the journal from accidental or malicious
+	modification. To protect against accidental modification, use a
+	crc algorithm, to protect against malicious modification, use a
+	hmac algorithm with a key.
+
+	This option is not needed when using internal-hash because in this
+	mode, the integrity of journal entries is checked when replaying
+	the journal. Thus, modified sector number would be detected at
+	this stage.
+
+
+The journal mode (D/J), buffer-sectors, journal-watermark, commit-time can
+be changed when reloading the target (load an inactive table and swap the
+tables with suspend and resume). The other arguments should not be changed
+when reloading the target because the layout of disk data depend on them
+and the reloaded target would be non-functional.
+
+
+The layout of the formatted block device:
+* reserved sectors (they are not used by this target, they can be used for
+  storing LUKS metadata or for other purpose), the size of the reserved
+  area is specified in the target arguments
+* superblock (4kiB)
+	* magic string - identifies that the device was formatted
+	* version
+	* log2(interleave sectors)
+	* integrity tag size
+	* the number of journal sections
+	* provided data sectors - the number of sectors that this target
+	  provides (i.e. the size of the device minus the size of all
+	  metadata and padding). The user of this target should not send
+	  bios that access data beyond the "provided data sectors" limit.
+	* flags - a flag is set if journal-mac is used
+* journal
+	The journal is divided into sections, each section contains:
+	* metadata area (4kiB), it contains journal entries
+	  every journal entry contains:
+		* logical sector (specifies where the data and tag should
+		  be written)
+		* last 8 bytes of data
+		* integrity tag (the size is specified in the superblock)
+	    every metadata sector ends with
+		* mac (8-bytes), all the macs in 8 metadata sectors form a
+		  64-byte value. It is used to store hmac of sector
+		  numbers in the journal section, to protect against a
+		  possibility that the attacker tampers with sector
+		  numbers in the journal.
+		* commit id
+	* data area (the size is variable; it depends on how many journal
+	  entries fit into the metadata area)
+	    every sector in the data area contains:
+		* data (504 bytes of data, the last 8 bytes are stored in
+		  the journal entry)
+		* commit id
+	To test if the whole journal section was written correctly, every
+	512-byte sector of the journal ends with 8-byte commit id. If the
+	commit id matches on all sectors in a journal section, then it is
+	assumed that the section was written correctly. If the commit id
+	doesn't match, the section was written partially and it should not
+	be replayed.
+* one or more runs of interleaved tags and data. Each run contains:
+	* tag area - it contains integrity tags. There is one tag for each
+	  sector in the data area
+	* data area - it contains data sectors. The number of data sectors
+	  in one run must be a power of two. log2 of this value is stored
+	  in the superblock.
drivers/md/Kconfig +10 -0
index b7767da50c26..0bac9ca2819d 100644
@@ -508,4 +508,14 @@ config DM_LOG_WRITES
 
 	  If unsure, say N.
 
+config DM_INTEGRITY
+	tristate "Integrity target"
+	depends on BLK_DEV_DM
+	select BLK_DEV_INTEGRITY
+	select DM_BUFIO
+	select CRYPTO
+	select ASYNC_XOR
+	---help---
+	   This is the integrity target.
+
 endif # MD
drivers/md/Makefile +1 -0
index 3cbda1af87a0..61270eadf5ec 100644
@@ -59,6 +59,7 @@ obj-$(CONFIG_DM_CACHE_SMQ)	+= dm-cache-smq.o
 obj-$(CONFIG_DM_CACHE_CLEANER)	+= dm-cache-cleaner.o
 obj-$(CONFIG_DM_ERA)		+= dm-era.o
 obj-$(CONFIG_DM_LOG_WRITES)	+= dm-log-writes.o
+obj-$(CONFIG_DM_INTEGRITY)	+= dm-integrity.o
 
 ifeq ($(CONFIG_DM_UEVENT),y)
 dm-mod-objs			+= dm-uevent.o
drivers/md/dm-integrity.c new file +3031 -0

2,790 lines not on this page · raw message

The rest of this diff is not shown. View the raw message.

Signature · 1 line
2.11.0

Part of the diff is not shown here. View the raw message.

author
#4 lore raw

RFC PATCH 4/4 Add cryptographic data integrity protection (authenticated encryption) to dm-crypt.

In reply to Milan Broz

This patch allows to use per-sector metadata provided by dm-integrity module for integrity protection and persistently stored per-sector Initialization Vector (IV). The underlying device must supports "DM-DIF-EXT-TAG" dm-integrity profile.

The per-bio integrity metadata are allocated by dm-crypt for every bio.

Example of low-level mapping table for various types of use:
DEV=/dev/sdb
SIZE=417792

# Additional HMAC with CBC-ESSIV, key is concatenated encryption key + HMAC key SIZE_INT=389952
dmsetup create x --table "0 $SIZE_INT integrity $DEV 0 32 J 0" dmsetup create y --table "0 $SIZE_INT crypt aes-cbc-essiv:sha256 \
11ff33c6fb942655efb3e30cf4c0fd95f5ef483afca72166c530ae26151dd83b \
00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff \
0 /dev/mapper/x 0 1 integrity:32:hmac(sha256)"

# AEAD (Authenticated Encryption with Additional Data) - GCM with random IVs # GCM in kernel uses 96bits IV and we store 128bits auth tag (so 28 bytes metadata space) SIZE_INT=393024
dmsetup create x --table "0 $SIZE_INT integrity $DEV 0 28 J 0"
dmsetup create y --table "0 $SIZE_INT crypt aes-gcm-random \
11ff33c6fb942655efb3e30cf4c0fd95f5ef483afca72166c530ae26151dd83b \
0 /dev/mapper/x 0 1 integrity:28:aead"

# Random IV only for XTS mode (no integrity protection but provides atomic random sector change) SIZE_INT=401272
dmsetup create x --table "0 $SIZE_INT integrity $DEV 0 16 J 0"
dmsetup create y --table "0 $SIZE_INT crypt aes-xts-random \
11ff33c6fb942655efb3e30cf4c0fd95f5ef483afca72166c530ae26151dd83b \
0 /dev/mapper/x 0 1 integrity:16:none"

# Random IV with XTS + HMAC integrity protection SIZE_INT=377656
dmsetup create x --table "0 $SIZE_INT integrity $DEV 0 48 J 0" dmsetup create y --table "0 $SIZE_INT crypt aes-xts-random \
11ff33c6fb942655efb3e30cf4c0fd95f5ef483afca72166c530ae26151dd83b \
00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff \
0 /dev/mapper/x 0 1 integrity:48:hmac(sha256)"

Both AEAD and HMAC protection authenticates not only data but also sector metadata.

HMAC protection is implemented through autenc wrapper (so it is processed the same way as a authenticated mode).

In HMAC mode there are two keys (concatenated in dm-crypt mapping table). First is the encryption key and the second is the key for authentication (HMAC). (It is userspace decision if these keys are independent or somehow derived.)

The sector request for AEAD/HMAC authenticated encryption looks like this:

 |----- AAD -------|------ DATA -------|-- AUTH TAG --|
 | (authenticated) | (auth+encryption) |              |
 | sector_LE |  IV |  sector in/out    |  tag in/out  |

For writes, the integrity fields are calculated during AEAD encryption of every sector and stored in bio integrity fields and sent to underlying dm-integrity target for storage.

For reads, the integrity metadata are verified during AEAD decryption of every sector (they are filled in in dm-integrity, but the integrity fields are pre-allocated in dm-crypt).

There is also an experimental support in cryptsetup utility (wip-integrity branch) for more friendly configuration (part of LUKS2 format).

Because the integrity fields are not valid on initial creation, the device must be "formatted". This can be done by direct-io writes to the device.
For now, there is available trivial tool to do this https://github.com/mbroz/dm_int_tools
(but dd is direct-io mode can be used as well).

---
 Documentation/device-mapper/dm-crypt.txt |  16 +
 drivers/md/dm-crypt.c                    | 857 ++++++++++++++++++++++++++-----
 2 files changed, 742 insertions(+), 131 deletions(-)
Documentation/device-mapper/dm-crypt.txt +16 -0
index ff1f87bf26e8..a2a6627aa659 100644
@@ -93,6 +93,22 @@ submit_from_crypt_cpus
     thread because it benefits CFQ to have writes submitted using the
     same context.
 
+integrity:<bytes>:<type>
+    Calculates and verifies integrity for the encrypted device (uses
+    authenticated encryption). This mode requires metadata stored in per-bio
+    integrity structure of <bytes> in size.
+
+    This option requires that the underlying device is created by dm-integrity
+    target and provides exactly <bytes> of per-sector metadata.
+
+    There can by two options for <type>. The first one is used when encryption
+    mode is Authenticated mode (AEAD mode), then type must be just "aead".
+    The second option is integrity calculated by keyed hash (HMAC), then
+    <type> is for example "hmac(sha256)".
+
+    If random IV is used (persistently stored IV in metadata per-sector),
+    then <bytes> includes both space for random IV and authentication tag.
+
 Example scripts
 ===============
 LUKS (Linux Unified Key Setup) is now the preferred way to set up disk
drivers/md/dm-crypt.c +726 -131

1,279 lines not on this page · raw message

Signature · 1 line
2.11.0
author
#5 lore raw

cover Data integrity protection with dm-integrity and dm-crypt

In reply to Milan Broz

These are additional patches to previously submitted integrity protection commits, based on for-next branch in Mike's DM tree.

Most of them probably can be folded-in, patches are separated for better readability.

The last patch for large encryption sectors is a new feature but because it depends on all previous patches it is posted in this series.

Milan Broz (7):
dm-crypt: Fix documentation of integrity table option.
dm-crypt: Move IV constructor to separate function.
dm-crypt: Introduce new format of cipher with capi: prefix.
dm-crypt: Compute HMAC key size in a separate function.
dm-crypt: Parse cipher specification according to AEAD flag.
dm-crypt: Remove obsolete integrity_mode function.
dm-crypt: optionally support larger encryption sector size

 Documentation/device-mapper/dm-crypt.txt |  65 +++--
 drivers/md/dm-crypt.c                    | 479 +++++++++++++++++++++----------
 2 files changed, 371 insertions(+), 173 deletions(-)
Signature · 1 line
2.11.0
author
#6 lore raw

PATCH 1/7 dm-crypt: Fix documentation of integrity table option.

This patch updates old documentation to really implemented version, previous "hmac" option was merged to the same processing path.

---
 Documentation/device-mapper/dm-crypt.txt | 20 ++++++++------------
 1 file changed, 8 insertions(+), 12 deletions(-)
Documentation/device-mapper/dm-crypt.txt +8 -12
index a2a6627aa659..058f26ddf875 100644
@@ -94,20 +94,16 @@ submit_from_crypt_cpus
     same context.
 
 integrity:<bytes>:<type>
-    Calculates and verifies integrity for the encrypted device (uses
-    authenticated encryption). This mode requires metadata stored in per-bio
-    integrity structure of <bytes> in size.
+    The device requires additional <bytes> metadata per-sector stored
+    in per-bio integrity structure. This metadata must by provided
+    by underlying dm-integrity target.
 
-    This option requires that the underlying device is created by dm-integrity
-    target and provides exactly <bytes> of per-sector metadata.
+    The <type> can be "none" if metadata is used only for persistent IV.
 
-    There can by two options for <type>. The first one is used when encryption
-    mode is Authenticated mode (AEAD mode), then type must be just "aead".
-    The second option is integrity calculated by keyed hash (HMAC), then
-    <type> is for example "hmac(sha256)".
-
-    If random IV is used (persistently stored IV in metadata per-sector),
-    then <bytes> includes both space for random IV and authentication tag.
+    For Authenticated Encryption with Additional Data (AEAD)
+    the <type> is "aead". An AEAD mode additionally calculates and verifies
+    integrity for the encrypted device. The additional space is then
+    used for storing authentication tag (and persistent IV if needed).
 
 Example scripts
 ===============
Signature · 1 line
2.11.0
author
#7 lore raw

PATCH 2/7 dm-crypt: Move IV constructor to separate function.

In reply to Milan Broz

No functional change in this patch, just preparation for next patches.

---
 drivers/md/dm-crypt.c | 130 +++++++++++++++++++++++++++-----------------------
 1 file changed, 69 insertions(+), 61 deletions(-)
drivers/md/dm-crypt.c +69 -61
index 9ec6d50603f6..faec408dcf50 100644
@@ -2196,6 +2196,73 @@ static void crypt_dtr(struct dm_target *ti)
 	kzfree(cc);
 }
 
+static int crypt_ctr_ivmode(struct dm_target *ti, const char *ivmode)
+{
+	struct crypt_config *cc = ti->private;
+
+	if (crypt_integrity_mode(cc))
+		cc->iv_size = crypto_aead_ivsize(any_tfm_aead(cc));
+	else
+		cc->iv_size = crypto_skcipher_ivsize(any_tfm(cc));
+
+	if (crypt_integrity_hmac(cc)) {
+		cc->authenc_key = kmalloc(crypt_authenckey_size(cc), GFP_KERNEL);
+		if (!cc->authenc_key) {
+			ti->error = "Error allocating authenc key space";
+			return -ENOMEM;
+		}
+	}
+
+	if (cc->iv_size)
+		/* at least a 64 bit sector number should fit in our buffer */
+		cc->iv_size = max(cc->iv_size,
+				  (unsigned int)(sizeof(u64) / sizeof(u8)));
+	else if (ivmode) {
+		DMWARN("Selected cipher does not support IVs");
+		ivmode = NULL;
+	}
+
+	/* Choose ivmode, see comments at iv code. */
+	if (ivmode == NULL)
+		cc->iv_gen_ops = NULL;
+	else if (strcmp(ivmode, "plain") == 0)
+		cc->iv_gen_ops = &crypt_iv_plain_ops;
+	else if (strcmp(ivmode, "plain64") == 0)
+		cc->iv_gen_ops = &crypt_iv_plain64_ops;
+	else if (strcmp(ivmode, "essiv") == 0)
+		cc->iv_gen_ops = &crypt_iv_essiv_ops;
+	else if (strcmp(ivmode, "benbi") == 0)
+		cc->iv_gen_ops = &crypt_iv_benbi_ops;
+	else if (strcmp(ivmode, "null") == 0)
+		cc->iv_gen_ops = &crypt_iv_null_ops;
+	else if (strcmp(ivmode, "lmk") == 0) {
+		cc->iv_gen_ops = &crypt_iv_lmk_ops;
+		/*
+		 * Version 2 and 3 is recognised according
+		 * to length of provided multi-key string.
+		 * If present (version 3), last key is used as IV seed.
+		 * All keys (including IV seed) are always the same size.
+		 */
+		if (cc->key_size % cc->key_parts) {
+			cc->key_parts++;
+			cc->key_extra_size = cc->key_size / cc->key_parts;
+		}
+	} else if (strcmp(ivmode, "tcw") == 0) {
+		cc->iv_gen_ops = &crypt_iv_tcw_ops;
+		cc->key_parts += 2; /* IV + whitening */
+		cc->key_extra_size = cc->iv_size + TCW_WHITENING_SIZE;
+	} else if (strcmp(ivmode, "random") == 0) {
+		cc->iv_gen_ops = &crypt_iv_random_ops;
+		/* Need storage space in integrity fields. */
+		cc->integrity_iv_size = cc->iv_size;
+	} else {
+		ti->error = "Invalid IV mode";
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
 static int crypt_ctr_cipher(struct dm_target *ti,
 			    char *cipher_in, char *key)
 {
@@ -2205,7 +2272,6 @@ static int crypt_ctr_cipher(struct dm_target *ti,
 	int ret = -EINVAL;
 	char dummy;
 
-	/* Convert to crypto api definition? */
 	if (strchr(cipher_in, '(')) {
 		ti->error = "Bad cipher specification";
 		return -EINVAL;
@@ -2276,67 +2342,9 @@ static int crypt_ctr_cipher(struct dm_target *ti,
 	}
 
 	/* Initialize IV */
-	if (crypt_integrity_mode(cc))
-		cc->iv_size = crypto_aead_ivsize(any_tfm_aead(cc));
-	else
-		cc->iv_size = crypto_skcipher_ivsize(any_tfm(cc));
-
-	if (crypt_integrity_hmac(cc)) {
-		cc->authenc_key = kmalloc(crypt_authenckey_size(cc), GFP_KERNEL);
-		if (!cc->authenc_key) {
-			ret = -ENOMEM;
-			ti->error = "Error allocating authenc key space";
-			goto bad;
-		}
-	}
-
-	if (cc->iv_size)
-		/* at least a 64 bit sector number should fit in our buffer */
-		cc->iv_size = max(cc->iv_size,
-				  (unsigned int)(sizeof(u64) / sizeof(u8)));
-	else if (ivmode) {
-		DMWARN("Selected cipher does not support IVs");
-		ivmode = NULL;
-	}
-
-	/* Choose ivmode, see comments at iv code. */
-	if (ivmode == NULL)
-		cc->iv_gen_ops = NULL;
-	else if (strcmp(ivmode, "plain") == 0)
-		cc->iv_gen_ops = &crypt_iv_plain_ops;
-	else if (strcmp(ivmode, "plain64") == 0)
-		cc->iv_gen_ops = &crypt_iv_plain64_ops;
-	else if (strcmp(ivmode, "essiv") == 0)
-		cc->iv_gen_ops = &crypt_iv_essiv_ops;
-	else if (strcmp(ivmode, "benbi") == 0)
-		cc->iv_gen_ops = &crypt_iv_benbi_ops;
-	else if (strcmp(ivmode, "null") == 0)
-		cc->iv_gen_ops = &crypt_iv_null_ops;
-	else if (strcmp(ivmode, "lmk") == 0) {
-		cc->iv_gen_ops = &crypt_iv_lmk_ops;
-		/*
-		 * Version 2 and 3 is recognised according
-		 * to length of provided multi-key string.
-		 * If present (version 3), last key is used as IV seed.
-		 * All keys (including IV seed) are always the same size.
-		 */
-		if (cc->key_size % cc->key_parts) {
-			cc->key_parts++;
-			cc->key_extra_size = cc->key_size / cc->key_parts;
-		}
-	} else if (strcmp(ivmode, "tcw") == 0) {
-		cc->iv_gen_ops = &crypt_iv_tcw_ops;
-		cc->key_parts += 2; /* IV + whitening */
-		cc->key_extra_size = cc->iv_size + TCW_WHITENING_SIZE;
-	} else if (strcmp(ivmode, "random") == 0) {
-		cc->iv_gen_ops = &crypt_iv_random_ops;
-		/* Need storage space in integrity fields. */
-		cc->integrity_iv_size = cc->iv_size;
-	} else {
-		ret = -EINVAL;
-		ti->error = "Invalid IV mode";
+	ret = crypt_ctr_ivmode(ti, ivmode);
+	if (ret < 0)
 		goto bad;
-	}
 
 	/* Initialize and set key */
 	ret = crypt_set_key(cc, key);
Signature · 1 line
2.11.0
author
#8 lore raw

PATCH 3/7 dm-crypt: Introduce new format of cipher with capi: prefix.

In reply to Milan Broz

For the new authenticated encryption we have to support generic composed modes (combination of encryption algorithm anf authenticator) because this is the way how kernel crypto API accesses such algorithms.

To simplify interface, we accept an algorithm directly in crypto API format. The new format is recognised by the "capi:" prefix. The dmcrypt internal IV specification is the same as for the old format.

The crypto API cipher specifications format is:
capi:cipher_api_spec-ivmode[:ivopts]
Examples:
capi:cbc(aes)-essiv:sha256 (equivalent to old aes-cbc-essiv:sha256)
capi:xts(aes)-plain64 (equivalent to old aes-xts-plain64)
Examples of authenticated modes:

     capi:gcm(aes)-random
     capi:authenc(hmac(sha256),xts(aes))-random
     capi:rfc7539(chacha20,poly1305)-random

Authenticated modes can be configured only by the new cipher format.

Authenticated encryption algorithms can be of two types, either native modes (like GCM) that performs both encryption and authentication internally, and also composed mode where user can compose AEAD with separate specification of encryption algorithm and authenticator.

Note that this policy allows user to specify arbitrary combination that can be insecure. (Policy decision is done in cryptsetup userspace.)

(Note that HMAC composed modes requires following patches in series that are split for better readability of changes.)

---
 Documentation/device-mapper/dm-crypt.txt |  27 +++++--
 drivers/md/dm-crypt.c                    | 134 +++++++++++++++++++++++++------
 2 files changed, 132 insertions(+), 29 deletions(-)
Documentation/device-mapper/dm-crypt.txt +22 -5
index 058f26ddf875..8140b71f3c54 100644
@@ -11,14 +11,31 @@ Parameters: <cipher> <key> <iv_offset> <device path> \
 	      <offset> [<#opt_params> <opt_params>]
 
 <cipher>
-    Encryption cipher and an optional IV generation mode.
-    (In format cipher[:keycount]-chainmode-ivmode[:ivopts]).
+    Encryption cipher, encryption mode and Initial Vector (IV) generator.
+
+    The cipher specifications format is:
+       cipher[:keycount]-chainmode-ivmode[:ivopts]
     Examples:
-       des
        aes-cbc-essiv:sha256
-       twofish-ecb
+       aes-xts-plain64
+       serpent-xts-plain64
+
+    Cipher format also supports direct specification with kernel crypt API
+    format (selected by capi: prefix). The IV specification is the same
+    as for the first format type.
+    This format is mainly used for specification of authenticated modes.
 
-    /proc/crypto contains supported crypto modes
+    The crypto API cipher specifications format is:
+        capi:cipher_api_spec-ivmode[:ivopts]
+    Examples:
+        capi:cbc(aes)-essiv:sha256
+        capi:xts(aes)-plain64
+    Examples of authenticated modes:
+        capi:gcm(aes)-random
+        capi:authenc(hmac(sha256),xts(aes))-random
+        capi:rfc7539(chacha20,poly1305)-random
+
+    The /proc/crypto contains a list of curently loaded crypto modes.
 
 <key>
     Key used for encryption. It is encoded either as a hexadecimal number
drivers/md/dm-crypt.c +110 -24

195 lines not on this page · raw message

Signature · 1 line
2.11.0
author
#9 lore raw

PATCH 4/7 dm-crypt: Compute HMAC key size in a separate function.

In reply to Milan Broz

For composed authenticated modes with HMAC (length-preserving encryption mode like a XTS and HMAC as an authenticator) we have to calculate HMAC digest size (the separate authentication key is as the same size as the HMAC digest).

This patch introduces workaround to parse crypto API string to get HMAC algorithm and retrieve digest size from it.

---
 drivers/md/dm-crypt.c | 50 +++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 49 insertions(+), 1 deletion(-)
drivers/md/dm-crypt.c +49 -1
index 0c7d07e17b81..48e8dfe91c53 100644
@@ -2293,6 +2293,45 @@ static int crypt_ctr_blkdev_cipher(struct crypt_config *cc)
 	return 0;
 }
 
+/*
+ * Workaround to parse HMAC algorithm from AEAD crypto API spec.
+ * The HMAC is needed to calculate tag size (HMAC digest size).
+ * This should be probably done by crypto-api calls (once available...)
+ */
+static int crypt_ctr_auth_cipher(struct crypt_config *cc, char *cipher_api)
+{
+	char *start, *end, *mac_alg = NULL;
+	struct crypto_ahash *mac;
+
+	if (!strstarts(cipher_api, "authenc("))
+		return 0;
+
+	start = strchr(cipher_api, '(');
+	end = strchr(cipher_api, ',');
+	if (!start || !end || ++start > end)
+		return -EINVAL;
+
+	mac_alg = kzalloc(end - start + 1, GFP_KERNEL);
+	if (!mac_alg)
+		return -ENOMEM;
+	strncpy(mac_alg, start, end - start);
+
+	mac = crypto_alloc_ahash(mac_alg, 0, 0);
+	kfree(mac_alg);
+
+	if (IS_ERR(mac))
+		return PTR_ERR(mac);
+
+	cc->key_mac_size = crypto_ahash_digestsize(mac);
+	crypto_free_ahash(mac);
+
+	cc->authenc_key = kmalloc(crypt_authenckey_size(cc), GFP_KERNEL);
+	if (!cc->authenc_key)
+		return -ENOMEM;
+
+	return 0;
+}
+
 static int crypt_ctr_cipher_new(struct dm_target *ti, char *cipher_in, char *key,
 				char **ivmode, char **ivopts)
 {
@@ -2323,7 +2362,16 @@ static int crypt_ctr_cipher_new(struct dm_target *ti, char *cipher_in, char *key
 		return ret;
 	}
 
-	cc->iv_size = crypto_skcipher_ivsize(any_tfm(cc));
+	/* Alloc AEAD, can be used only in new format. */
+	if (crypt_integrity_aead(cc)) {
+		ret = crypt_ctr_auth_cipher(cc, cipher_api);
+		if (ret < 0) {
+			ti->error = "Invalid AEAD cipher spec";
+			return -ENOMEM;
+		}
+		cc->iv_size = crypto_aead_ivsize(any_tfm_aead(cc));
+	} else
+		cc->iv_size = crypto_skcipher_ivsize(any_tfm(cc));
 
 	ret = crypt_ctr_blkdev_cipher(cc);
 	if (ret < 0) {
Signature · 1 line
2.11.0
author
#10 lore raw

PATCH 5/7 dm-crypt: Parse cipher specification according to AEAD flag.

In reply to Milan Broz

This patch siplifies allocation of HMAC composed mode by parsing the new cipher format directly.

For native AEAD mode (like GCM), we can use crypto_tfm_alg_name() API to get the cipher specification, for HMAC composed mode we need to parse crypto API string to get cipher mode nested in specification.

---
 drivers/md/dm-crypt.c | 49 +++++++++++++++++--------------------------------
 1 file changed, 17 insertions(+), 32 deletions(-)
drivers/md/dm-crypt.c +17 -32
index 48e8dfe91c53..3a4bf5791a3b 100644
@@ -873,12 +873,12 @@ static bool crypt_integrity_aead(struct crypt_config *cc)
 
 static bool crypt_integrity_hmac(struct crypt_config *cc)
 {
-	return test_bit(CRYPT_MODE_INTEGRITY_HMAC, &cc->cipher_flags);
+	return crypt_integrity_aead(cc) && cc->key_mac_size;
 }
 
 static bool crypt_integrity_mode(struct crypt_config *cc)
 {
-	return crypt_integrity_aead(cc) || crypt_integrity_hmac(cc);
+	return crypt_integrity_aead(cc);
 }
 
 /* Get sg containing data */
@@ -1879,27 +1879,12 @@ static int crypt_alloc_tfms_skcipher(struct crypt_config *cc, char *ciphermode)
 
 static int crypt_alloc_tfms_aead(struct crypt_config *cc, char *ciphermode)
 {
-	char *authenc = NULL;
 	int err;
 
 	cc->cipher_tfm.tfms = kmalloc(sizeof(struct crypto_aead *), GFP_KERNEL);
 	if (!cc->cipher_tfm.tfms)
 		return -ENOMEM;
 
-	/* Compose AEAD cipher with autenc(authenticator,cipher) structure */
-	if (crypt_integrity_hmac(cc)) {
-		authenc = kmalloc(CRYPTO_MAX_ALG_NAME, GFP_KERNEL);
-		if (!authenc)
-			return -ENOMEM;
-		err = snprintf(authenc, CRYPTO_MAX_ALG_NAME,
-		       "authenc(%s,%s)", cc->cipher_auth, ciphermode);
-		if (err < 0) {
-			kzfree(authenc);
-			return err;
-		}
-		ciphermode = authenc;
-	}
-
 	cc->cipher_tfm.tfms_aead[0] = crypto_alloc_aead(ciphermode, 0, 0);
 	if (IS_ERR(cc->cipher_tfm.tfms_aead[0])) {
 		err = PTR_ERR(cc->cipher_tfm.tfms_aead[0]);
@@ -1907,7 +1892,6 @@ static int crypt_alloc_tfms_aead(struct crypt_config *cc, char *ciphermode)
 		return err;
 	}
 
-	kzfree(authenc);
 	return 0;
 }
 
@@ -1964,13 +1948,13 @@ static int crypt_setkey(struct crypt_config *cc)
 				      subkey_size - cc->key_mac_size,
 				      cc->key_mac_size);
 	for (i = 0; i < cc->tfms_count; i++) {
-		if (crypt_integrity_aead(cc))
-			r = crypto_aead_setkey(cc->cipher_tfm.tfms_aead[i],
-						   cc->key + (i * subkey_size),
-						   subkey_size);
-		else if (crypt_integrity_hmac(cc))
+		if (crypt_integrity_hmac(cc))
 			r = crypto_aead_setkey(cc->cipher_tfm.tfms_aead[i],
 				cc->authenc_key, crypt_authenckey_size(cc));
+		else if (crypt_integrity_aead(cc))
+			r = crypto_aead_setkey(cc->cipher_tfm.tfms_aead[i],
+						cc->key + (i * subkey_size),
+						subkey_size);
 		else
 			r = crypto_skcipher_setkey(cc->cipher_tfm.tfms[i],
 						   cc->key + (i * subkey_size),
@@ -2205,14 +2189,6 @@ static int crypt_ctr_ivmode(struct dm_target *ti, const char *ivmode)
 	else
 		cc->iv_size = crypto_skcipher_ivsize(any_tfm(cc));
 
-	if (crypt_integrity_hmac(cc)) {
-		cc->authenc_key = kmalloc(crypt_authenckey_size(cc), GFP_KERNEL);
-		if (!cc->authenc_key) {
-			ti->error = "Error allocating authenc key space";
-			return -ENOMEM;
-		}
-	}
-
 	if (cc->iv_size)
 		/* at least a 64 bit sector number should fit in our buffer */
 		cc->iv_size = max(cc->iv_size,
@@ -2270,9 +2246,18 @@ static int crypt_ctr_ivmode(struct dm_target *ti, const char *ivmode)
  */
 static int crypt_ctr_blkdev_cipher(struct crypt_config *cc)
 {
-	const char *alg_name = crypto_tfm_alg_name(crypto_skcipher_tfm(any_tfm(cc)));
+	const char *alg_name = NULL;
 	char *start, *end;
 
+	if (crypt_integrity_aead(cc)) {
+		if (!(alg_name = crypto_tfm_alg_name(crypto_aead_tfm(any_tfm_aead(cc)))))
+			return -EINVAL;
+		if (crypt_integrity_hmac(cc) && !(alg_name = strchr(alg_name, ',')))
+			return -EINVAL;
+		alg_name++;
+	} else if (!(alg_name = crypto_tfm_alg_name(crypto_skcipher_tfm(any_tfm(cc)))))
+		return -EINVAL;
+
 	start = strchr(alg_name, '(');
 	end = strchr(alg_name, ')');
 
Signature · 1 line
2.11.0
author
#11 lore raw

PATCH 6/7 dm-crypt: Remove obsolete integrity_mode function.

In reply to Milan Broz

The HMAC composed mode is not processed the same as the normal AEAD mode, so this patch removes no longer needed flag and "hmac" specification for the table integrity argument.

---
 drivers/md/dm-crypt.c | 41 +++++++++++++----------------------------
 1 file changed, 13 insertions(+), 28 deletions(-)
drivers/md/dm-crypt.c +13 -28
index 3a4bf5791a3b..270adba14717 100644
@@ -129,7 +129,6 @@ enum flags { DM_CRYPT_SUSPENDED, DM_CRYPT_KEY_VALID,
 
 enum cipher_flags {
 	CRYPT_MODE_INTEGRITY_AEAD,	/* Use authenticated mode for cihper */
-	CRYPT_MODE_INTEGRITY_HMAC,	/* Compose authenticated mode from normal mode and HMAC */
 };
 
 /*
@@ -876,16 +875,11 @@ static bool crypt_integrity_hmac(struct crypt_config *cc)
 	return crypt_integrity_aead(cc) && cc->key_mac_size;
 }
 
-static bool crypt_integrity_mode(struct crypt_config *cc)
-{
-	return crypt_integrity_aead(cc);
-}
-
 /* Get sg containing data */
 static struct scatterlist *crypt_get_sg_data(struct crypt_config *cc,
 					     struct scatterlist *sg)
 {
-	if (unlikely(crypt_integrity_mode(cc)))
+	if (unlikely(crypt_integrity_aead(cc)))
 		return &sg[2];
 
 	return sg;
@@ -936,7 +930,7 @@ static int crypt_integrity_ctr(struct crypt_config *cc, struct dm_target *ti)
 		return -EINVAL;
 	}
 
-	if (crypt_integrity_mode(cc)) {
+	if (crypt_integrity_aead(cc)) {
 		cc->integrity_tag_size = cc->on_disk_tag_size - cc->integrity_iv_size;
 		DMINFO("Integrity AEAD, tag size %u, IV size %u.",
 		       cc->integrity_tag_size, cc->integrity_iv_size);
@@ -990,7 +984,7 @@ static void *req_of_dmreq(struct crypt_config *cc, struct dm_crypt_request *dmre
 static u8 *iv_of_dmreq(struct crypt_config *cc,
 		       struct dm_crypt_request *dmreq)
 {
-	if (crypt_integrity_mode(cc))
+	if (crypt_integrity_aead(cc))
 		return (u8 *)ALIGN((unsigned long)(dmreq + 1),
 			crypto_aead_alignmask(any_tfm_aead(cc)) + 1);
 	else
@@ -1235,7 +1229,7 @@ static void crypt_alloc_req_aead(struct crypt_config *cc,
 static void crypt_alloc_req(struct crypt_config *cc,
 			    struct convert_context *ctx)
 {
-	if (crypt_integrity_mode(cc))
+	if (crypt_integrity_aead(cc))
 		crypt_alloc_req_aead(cc, ctx);
 	else
 		crypt_alloc_req_skcipher(cc, ctx);
@@ -1261,7 +1255,7 @@ static void crypt_free_req_aead(struct crypt_config *cc,
 
 static void crypt_free_req(struct crypt_config *cc, void *req, struct bio *base_bio)
 {
-	if (crypt_integrity_mode(cc))
+	if (crypt_integrity_aead(cc))
 		crypt_free_req_aead(cc, req, base_bio);
 	else
 		crypt_free_req_skcipher(cc, req, base_bio);
@@ -1284,7 +1278,7 @@ static int crypt_convert(struct crypt_config *cc,
 
 		atomic_inc(&ctx->cc_pending);
 
-		if (crypt_integrity_mode(cc))
+		if (crypt_integrity_aead(cc))
 			r = crypt_convert_block_aead(cc, ctx, ctx->r.req_aead, tag_offset);
 		else
 			r = crypt_convert_block_skcipher(cc, ctx, ctx->r.req, tag_offset);
@@ -1849,7 +1843,7 @@ static void crypt_free_tfms_skcipher(struct crypt_config *cc)
 
 static void crypt_free_tfms(struct crypt_config *cc)
 {
-	if (crypt_integrity_mode(cc))
+	if (crypt_integrity_aead(cc))
 		crypt_free_tfms_aead(cc);
 	else
 		crypt_free_tfms_skcipher(cc);
@@ -1897,7 +1891,7 @@ static int crypt_alloc_tfms_aead(struct crypt_config *cc, char *ciphermode)
 
 static int crypt_alloc_tfms(struct crypt_config *cc, char *ciphermode)
 {
-	if (crypt_integrity_mode(cc))
+	if (crypt_integrity_aead(cc))
 		return crypt_alloc_tfms_aead(cc, ciphermode);
 	else
 		return crypt_alloc_tfms_skcipher(cc, ciphermode);
@@ -2184,7 +2178,7 @@ static int crypt_ctr_ivmode(struct dm_target *ti, const char *ivmode)
 {
 	struct crypt_config *cc = ti->private;
 
-	if (crypt_integrity_mode(cc))
+	if (crypt_integrity_aead(cc))
 		cc->iv_size = crypto_aead_ivsize(any_tfm_aead(cc));
 	else
 		cc->iv_size = crypto_skcipher_ivsize(any_tfm(cc));
@@ -2376,7 +2370,7 @@ static int crypt_ctr_cipher_old(struct dm_target *ti, char *cipher_in, char *key
 	int ret = -EINVAL;
 	char dummy;
 
-	if (strchr(cipher_in, '(')) {
+	if (strchr(cipher_in, '(') || crypt_integrity_aead(cc)) {
 		ti->error = "Bad cipher specification";
 		return -EINVAL;
 	}
@@ -2543,15 +2537,6 @@ static int crypt_ctr_optional(struct dm_target *ti, unsigned int argc, char **ar
 			}
 			if (!strcasecmp(sval, "aead")) {
 				set_bit(CRYPT_MODE_INTEGRITY_AEAD, &cc->cipher_flags);
-			} else  if (!strncasecmp(sval, "hmac(", strlen("hmac("))) {
-				struct crypto_ahash *hmac_tfm = crypto_alloc_ahash(sval, 0, 0);
-				if (IS_ERR(hmac_tfm)) {
-					ti->error = "Error initializing HMAC integrity hash.";
-					return PTR_ERR(hmac_tfm);
-				}
-				cc->key_mac_size = crypto_ahash_digestsize(hmac_tfm);
-				crypto_free_ahash(hmac_tfm);
-				set_bit(CRYPT_MODE_INTEGRITY_HMAC, &cc->cipher_flags);
 			} else  if (strcasecmp(sval, "none")) {
 				ti->error = "Unknown integrity profile";
 				return -EINVAL;
@@ -2614,7 +2599,7 @@ static int crypt_ctr(struct dm_target *ti, unsigned int argc, char **argv)
 	if (ret < 0)
 		goto bad;
 
-	if (crypt_integrity_mode(cc)) {
+	if (crypt_integrity_aead(cc)) {
 		cc->dmreq_start = sizeof(struct aead_request);
 		cc->dmreq_start += crypto_aead_reqsize(any_tfm_aead(cc));
 		align_mask = crypto_aead_alignmask(any_tfm_aead(cc));
@@ -2691,7 +2676,7 @@ static int crypt_ctr(struct dm_target *ti, unsigned int argc, char **argv)
 	}
 	cc->start = tmpll;
 
-	if (crypt_integrity_mode(cc) || cc->integrity_iv_size) {
+	if (crypt_integrity_aead(cc) || cc->integrity_iv_size) {
 		ret = crypt_integrity_ctr(cc, ti);
 		if (ret)
 			goto bad;
@@ -2789,7 +2774,7 @@ static int crypt_map(struct dm_target *ti, struct bio *bio)
 		}
 	}
 
-	if (crypt_integrity_mode(cc))
+	if (crypt_integrity_aead(cc))
 		io->ctx.r.req_aead = (struct aead_request *)(io + 1);
 	else
 		io->ctx.r.req = (struct skcipher_request *)(io + 1);
Signature · 1 line
2.11.0
author
#12 lore raw

PATCH 7/7 dm-crypt: optionally support larger encryption sector size

In reply to Milan Broz

This patch adds optional "sector_size" parameter that specifies encryption sector size (atomic unit of block device encryption).

Parameter can be in range 512 - 4096 bytes and must be power of two. For compatibility reasons, the maximal IO must fit into the page limit, so the limit is set to minimal page size possible (4096 bytes).

NOTE: this device cannot be handled yet with cryptsetup directly if this parameter is set.

IV for sector is calculated from the 512 bytes sector offset until the iv_large_sectors option is used.

Test script using dmsetup:

  DEV="/dev/sdb"
  DEV_SIZE=$(blockdev --getsz $DEV)
  KEY="9c1185a5c5e9fc54612808977ee8f548b2258d31ddadef707ba62c166051b9e3cd0294c27515f2bccee924e8823ca6e124b8fc3167ed478bca702babe4e130ac"
  BLOCK_SIZE=4096

  dmsetup create test_crypt --table "0 $DEV_SIZE crypt aes-xts-plain64 $KEY 0 $DEV 0 1 sector_size:$BLOCK_SIZE"
  #dmsetup table --showkeys test_crypt
---
 Documentation/device-mapper/dm-crypt.txt |  14 +++++
 drivers/md/dm-crypt.c                    | 105 ++++++++++++++++++++++++-------
 2 files changed, 96 insertions(+), 23 deletions(-)
Documentation/device-mapper/dm-crypt.txt +14 -0
index 8140b71f3c54..3b3e1de21c9c 100644
@@ -122,6 +122,20 @@ integrity:<bytes>:<type>
     integrity for the encrypted device. The additional space is then
     used for storing authentication tag (and persistent IV if needed).
 
+sector_size:<bytes>
+    Use <bytes> as the encryption unit instead of 512 bytes sectors.
+    This option can be in range 512 - 4096 bytes and must be power of two.
+    Virtual device will announce this size as a minimal IO and logical sector.
+
+iv_large_sectors
+   IV generators will use sector number counted in <sector_size> units
+   instead of default 512 bytes sectors.
+
+   For example, if <sector_size> is 4096 bytes, plain64 IV for the second
+   sector will be 8 (without flag) and 1 if iv_large_sectors is present.
+   The <iv_offset> must be multiple of <sector_size> (in 512 bytes units)
+   if this flag is specified.
+
 Example scripts
 ===============
 LUKS (Linux Unified Key Setup) is now the preferred way to set up disk
drivers/md/dm-crypt.c +82 -23

300 lines not on this page · raw message

Signature · 1 line
2.11.0
#13 lore raw
In reply to Milan Broz
Quoted 7 lines · Milan Broz

These are additional patches to previously submitted integrity protection commits, based on for-next branch in Mike's DM tree.

Most of them probably can be folded-in, patches are separated for better readability.

The last patch for large encryption sectors is a new feature but because it depends on all previous patches it is posted in this series.

I've picked all these up and staged in linux-next and dm-4.12, see:
https://git.kernel.org/pub/scm/linux/kernel/git/device-mapper/linux-dm.git/log/?h=dm-4.12

(you'll note that I folded them and tweaked headers accordingly)