diff mbox series

cpio_utils: flush pending plaintext before signaling EOF

Message ID 20260808131832.8161-1-bage@debian.org
State New
Delegated to: Stefano Babic
Headers show
Series cpio_utils: flush pending plaintext before signaling EOF | expand

Commit Message

Bastian Germann Aug. 8, 2026, 1:18 p.m. UTC
The p11-kit PKCS#11 decrypt backend holds back the last AES
block internally and only emits it on the next DECRYPT_update
call or on DECRYPT_final. For ciphertext fitting into a single
block (e.g. a 6-byte version string), the only DECRYPT_update
call yields no output and decrypt_step returned zero, which
copyfile() treats as end-of-stream. DECRYPT_final was then
never called and the file was written empty.

Let decrypt_step() keep pulling input until decrypted output
is available or the stream is fully finalized.

Signed-off-by: Bastian Germann <bage@debian.org>
---
 core/cpio_utils.c        | 47 ++++++++++++----------------
 test/Makefile            |  4 +++
 test/test_crypt_pkcs11.c | 66 +++++++++++++++++++++++++++++++++++++++-
 3 files changed, 88 insertions(+), 29 deletions(-)
diff mbox series

Patch

diff --git a/core/cpio_utils.c b/core/cpio_utils.c
index 9faa8019..ff417d7e 100644
--- a/core/cpio_utils.c
+++ b/core/cpio_utils.c
@@ -302,29 +302,32 @@  static int decrypt_step(void *state, void *buffer, size_t size)
 	int ret;
 	int inlen;
 
-	if (s->outlen != 0) {
-		if ((int)size > s->outlen) {
-			size = s->outlen;
+	for (;;) {
+		if (s->outlen != 0) {
+			if ((int)size > s->outlen) {
+				size = s->outlen;
+			}
+			memcpy(buffer, s->output, size);
+			s->outlen -= size;
+			memmove(s->output, s->output + size, s->outlen);
+			return size;
 		}
-		memcpy(buffer, s->output, size);
-		s->outlen -= size;
-		memmove(s->output, s->output + size, s->outlen);
-		return size;
-	}
 
-	ret = s->upstream_step(s->upstream_state, s->input, sizeof s->input);
-	if (ret < 0) {
-		return ret;
-	}
+		if (s->eof) {
+			return 0;
+		}
 
-	inlen = ret;
+		ret = s->upstream_step(s->upstream_state, s->input, sizeof s->input);
+		if (ret < 0) {
+			return ret;
+		}
+
+		inlen = ret;
 
-	if (!s->eof) {
 		if (inlen != 0) {
 			ret = swupdate_DECRYPT_update(s->dcrypt,
 				s->output, &s->outlen, s->input, inlen);
-		}
-		if (inlen == 0) {
+		} else {
 			/*
 			 * Finalise the decryption. Further plaintext bytes may
 			 * be written at this stage.
@@ -339,18 +342,6 @@  static int decrypt_step(void *state, void *buffer, size_t size)
 			return ret;
 		}
 	}
-
-	if (s->outlen != 0) {
-		if ((int)size > s->outlen) {
-			size = s->outlen;
-		}
-		memcpy(buffer, s->output, size);
-		s->outlen -= size;
-		memmove(s->output, s->output + size, s->outlen);
-		return size;
-	}
-
-	return 0;
 }
 
 #if defined(CONFIG_GUNZIP) || defined(CONFIG_ZSTD) || defined(CONFIG_XZ) || defined(CONFIG_LZ4)
diff --git a/test/Makefile b/test/Makefile
index ca66f7b0..3cf76946 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -180,6 +180,10 @@  $(DATADIR)/softshm: $(DATADIR)/token/softhsm.conf PREPARE_DATA
 	$(Q)openssl enc -aes-256-cbc -in $(DATADIR)/token/original.data -out $(DATADIR)/token/encrypted.data -K $(TOKEN_AES_KEY) -iv $(TOKEN_AES_IV)
 	$(Q)echo -n "$(TOKEN_AES_IV)" | xxd -p -r > $(DATADIR)/token/encrypted.data.iv
 
+	$(if $(Q),@echo "  GEN      $(DATADIR)/token/small.data")
+	$(Q)printf "1.2.3\n" > $(DATADIR)/token/small.data
+	$(Q)openssl enc -aes-256-cbc -in $(DATADIR)/token/small.data -out $(DATADIR)/token/small.encrypted.data -K $(TOKEN_AES_KEY) -iv $(TOKEN_AES_IV)
+
 	$(if $(Q),@echo "  IMPORT   $(DATADIR)/token/aes.key")
 	$(Q)echo -n "$(TOKEN_AES_KEY)" | xxd -p -r > $(DATADIR)/token/aes.key
 	$(Q)softhsm2-util --init-token --slot 0 --label "TestToken" --so-pin 123456 --pin 1234
diff --git a/test/test_crypt_pkcs11.c b/test/test_crypt_pkcs11.c
index f7b851ef..2c399cf9 100644
--- a/test/test_crypt_pkcs11.c
+++ b/test/test_crypt_pkcs11.c
@@ -7,6 +7,9 @@ 
 #include <stdarg.h>
 #include <stddef.h>
 #include <setjmp.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
 #include <cmocka.h>
 #include <util.h>
 #include "swupdate_crypto.h"
@@ -14,6 +17,8 @@ 
 #define BUFFER_SIZE (AES_BLK_SIZE * 1024)
 #define TOKENDIR "test/data/token"
 
+static unsigned char small_data[AES_BLK_SIZE];
+
 static int read_file(const char *path, unsigned char *buffer, size_t *size)
 {
 	FILE *fp = fopen(path, "r");
@@ -90,10 +95,69 @@  static void test_crypt_pkcs11_256(void **state)
 	assert_true(strncmp((const char *)decrypted_data, (const char *)original_data, original_data_len) == 0);
 }
 
+static size_t captured_len = 0;
+
+static int capture_output(void *out, const void *buf, size_t len)
+{
+	(void)out;
+	assert_true(captured_len + len <= sizeof(small_data));
+	memcpy(&small_data[captured_len], buf, len);
+	captured_len += len;
+	return 0;
+}
+
+/*
+ * Regression test: decrypting a file that fits into a single AES block
+ * must yield its plaintext. Files of 16 bytes or less (e.g. a version
+ * string) have their whole plaintext buffered inside the decrypt context
+ * until the final call, so the copy pipeline must not stop early.
+ */
+static void test_crypt_pkcs11_single_block(void **state)
+{
+	(void) state;
+	int err;
+
+	static const char *uri =
+		"pkcs11:token=TestToken;id=%A1%B2?pin-value=1234&module-path=/usr/lib/softhsm/libsofthsm2.so";
+
+	static const char version[] = "1.2.3\n";
+
+	err = set_aes_key(uri, "c1f390d21dd06118cbd333144a3318ca");
+	assert_true(err == 0);
+
+	struct stat sb;
+	err = stat(TOKENDIR "/small.encrypted.data", &sb);
+	assert_true(err == 0);
+	assert_true(sb.st_size == AES_BLK_SIZE);
+
+	unsigned long offs = 0;
+	int fdin = open(TOKENDIR "/small.encrypted.data", O_RDONLY);
+	assert_true(fdin >= 0);
+
+	struct swupdate_copy copy = {
+		.fdin = fdin,
+		.out = NULL,
+		.callback = capture_output,
+		.nbytes = sb.st_size,
+		.offs = &offs,
+		.encrypted = true,
+		.cipher = AES_CBC,
+	};
+
+	captured_len = 0;
+	err = copyfile(&copy);
+	close(fdin);
+	assert_true(err == 0);
+
+	assert_true(captured_len == sizeof(version) - 1);
+	assert_true(memcmp(small_data, version, captured_len) == 0);
+}
+
 int main(void)
 {
 	const struct CMUnitTest crypt_pkcs11_tests[] = {
-		cmocka_unit_test(test_crypt_pkcs11_256)
+		cmocka_unit_test(test_crypt_pkcs11_256),
+		cmocka_unit_test(test_crypt_pkcs11_single_block)
 	};
 	return cmocka_run_group_tests_name("crypt_pkcs11", crypt_pkcs11_tests, NULL, NULL);
 }