From c8c22869e780ff57c96b46939c3d79ff99395f87 Mon Sep 17 00:00:00 2001
From: Lasse Collin <lasse.collin@tukaani.org>
Date: Sun, 29 Mar 2026 19:11:21 +0300
Subject: [PATCH] liblzma: Fix a buffer overflow in lzma_index_append()

If lzma_index_decoder() was used to decode an Index that contained no
Records, the resulting lzma_index had an invalid internal "prealloc"
value. If lzma_index_append() was called on this lzma_index, too
little memory would be allocated and a buffer overflow would occur.

While this combination of the API functions is meant to work, in the
real-world apps this call sequence is rare or might not exist at all.

This bug is older than xz 5.0.0, so all stable releases are affected.

Reported-by: GitHub user christos-spearbit
---
 src/liblzma/common/index.c | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

Index: b/src/liblzma/common/index.c
===================================================================
--- a/src/liblzma/common/index.c
+++ b/src/liblzma/common/index.c
@@ -434,6 +434,26 @@ lzma_index_prealloc(lzma_index *i, lzma_
 	if (records > PREALLOC_MAX)
 		records = PREALLOC_MAX;
 
+	// If index_decoder.c calls us with records == 0, it's decoding
+	// an Index that has no Records. In that case the decoder won't call
+	// lzma_index_append() at all, and i->prealloc isn't used during
+	// the Index decoding either.
+	//
+	// Normally the first lzma_index_append() call from the Index decoder
+	// would reset i->prealloc to INDEX_GROUP_SIZE. With no Records,
+	// lzma_index_append() isn't called and the resetting of prealloc
+	// won't occur either. Thus, if records == 0, use the default value
+	// INDEX_GROUP_SIZE instead.
+	//
+	// NOTE: lzma_index_append() assumes i->prealloc > 0. liblzma <= 5.8.2
+	// didn't have this check and could set i->prealloc = 0, which would
+	// result in a buffer overflow if the application called
+	// lzma_index_append() after decoding an empty Index. Appending
+	// Records after decoding an Index is a rare thing to do, but
+	// it is supposed to work.
+	if (records == 0)
+		records = INDEX_GROUP_SIZE;
+
 	i->prealloc = (size_t)(records);
 	return;
 }
@@ -680,6 +700,7 @@ lzma_index_append(lzma_index *i, const l
 		++g->last;
 	} else {
 		// We need to allocate a new group.
+		assert(i->prealloc > 0);
 		g = lzma_alloc(sizeof(index_group)
 				+ i->prealloc * sizeof(index_record),
 				allocator);
Index: b/tests/test_index.c
===================================================================
--- a/tests/test_index.c
+++ b/tests/test_index.c
@@ -1758,6 +1758,40 @@ test_lzma_index_buffer_decode(void)
 }


+// With liblzma <= 5.8.2 (before the commit c8c22869e780),
+// this triggers a buffer overflow in lzma_index_append().
+static void
+test_decode_empty_and_append(void)
+{
+#if !defined(HAVE_ENCODERS) || !defined(HAVE_DECODERS)
+	assert_skip("Encoder or decoder support disabled");
+#else
+	uint8_t buf[256];
+	lzma_index *idx = lzma_index_init(NULL);
+	assert_true(idx != NULL);
+
+	// Encode an empty Index.
+	size_t buf_size = 0;
+	assert_lzma_ret(lzma_index_buffer_encode(
+			idx, buf, &buf_size, sizeof(buf)), LZMA_OK);
+	assert_true(buf_size > 0);
+	lzma_index_end(idx, NULL);
+	idx = NULL;
+
+	// Decode the empty Index.
+	uint64_t memlimit = MEMLIMIT;
+	size_t buf_pos = 0;
+	assert_lzma_ret(lzma_index_buffer_decode(&idx, &memlimit, NULL,
+			buf, &buf_pos, buf_size), LZMA_OK);
+	assert_uint_eq(buf_pos, buf_size);
+
+	// Append one Record to the decoded empty idx.
+	assert_lzma_ret(lzma_index_append(idx, NULL, 55, 1), LZMA_OK);
+	lzma_index_end(idx, NULL);
+#endif
+}
+
+
 extern int
 main(int argc, char **argv)
 {
@@ -1786,6 +1820,7 @@ main(int argc, char **argv)
 	tuktest_run(test_lzma_index_decoder);
 	tuktest_run(test_lzma_index_buffer_encode);
 	tuktest_run(test_lzma_index_buffer_decode);
+	tuktest_run(test_decode_empty_and_append);
 	lzma_index_end(decode_test_index, NULL);
 	return tuktest_end();
 }

