From 68b96608d0ef840ecb907080c1281cbbfee3f39e Mon Sep 17 00:00:00 2001 From: Hugo Hurskainen Date: Tue, 9 Jun 2026 18:02:06 +0300 Subject: [PATCH 1/3] fix: write NUL terminator for MAM coherency signature Zero the buffer so every byte written to the MAM is defined. In particular the "LTFS\0" signature below is copied with arch_strncpy(...,"LTFS",5,4), which on Linux/Mac maps to strncpy(dst,"LTFS",4) and does NOT write the 5th (NUL) byte. tape_get_cart_coherency() validates the signature with strncmp(...,"LTFS",5), so leaving that byte as uninitialised stack made coherency validation depend on garbage: when non-zero, every mount fails with LTFS12062W and falls back to a full medium consistency check. --- src/libltfs/tape.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libltfs/tape.c b/src/libltfs/tape.c index f971cb79..2141b369 100644 --- a/src/libltfs/tape.c +++ b/src/libltfs/tape.c @@ -1769,6 +1769,10 @@ int tape_set_cart_coherency(struct device_data *dev, const tape_partition_t part int ret; unsigned char coh_data[TC_MAM_PAGE_COHERENCY_SIZE + TC_MAM_PAGE_HEADER_SIZE]; + /* Zero the buffer so the "LTFS\0" signature's NUL terminator is written: the + * arch_strncpy below only copies 4 bytes, and the reader checks all 5. */ + memset(coh_data, 0, sizeof(coh_data)); + CHECK_ARG_NULL(dev, -LTFS_NULL_ARG); CHECK_ARG_NULL(dev->backend, -LTFS_NULL_ARG); From 1e605f47c4eb963ea51694380b3d48e794c19bd0 Mon Sep 17 00:00:00 2001 From: Hugo Hurskainen Date: Sun, 14 Jun 2026 23:02:38 +0300 Subject: [PATCH 2/3] refactor: harden MAM coherency signature handling No change to the on-medium bytes; this hardens how the "LTFS" volume coherency signature is written and checked: - Write the signature with memcpy of the full "LTFS\0" (5 bytes) instead of arch_strncpy(..., 5, 4), which copied only 4 bytes and relied on the preceding memset to supply the NUL terminator. The NUL is now explicit. - Move the literal into TC_MAM_COHERENCY_SIGNATURE (tape_ops.h) so the writer and reader share one definition of the magic value and its compared length. - Add _Static_asserts that the page buffer is large enough to hold every fixed field offset that is written, and that the signature is 4 chars plus a NUL, catching regressions at compile time. --- src/libltfs/tape.c | 24 +++++++++++++++++++----- src/libltfs/tape_ops.h | 1 + 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/libltfs/tape.c b/src/libltfs/tape.c index 2141b369..944ee5b9 100644 --- a/src/libltfs/tape.c +++ b/src/libltfs/tape.c @@ -1741,7 +1741,8 @@ int tape_get_cart_coherency(struct device_data *dev, const tape_partition_t part if (ap_clent_specific_len != 42 && ap_clent_specific_len != 43) { ltfsmsg(LTFS_WARN, 12061W, ap_clent_specific_len); return -LTFS_UNEXPECTED_VALUE; - } else if (strncmp((char *)coh_data + 32, "LTFS", sizeof("LTFS")) != 0) { + } else if (strncmp((char *)coh_data + 32, TC_MAM_COHERENCY_SIGNATURE, + sizeof(TC_MAM_COHERENCY_SIGNATURE)) != 0) { ltfsmsg(LTFS_WARN, 12062W); return -LTFS_UNEXPECTED_VALUE; } @@ -1769,8 +1770,16 @@ int tape_set_cart_coherency(struct device_data *dev, const tape_partition_t part int ret; unsigned char coh_data[TC_MAM_PAGE_COHERENCY_SIZE + TC_MAM_PAGE_HEADER_SIZE]; - /* Zero the buffer so the "LTFS\0" signature's NUL terminator is written: the - * arch_strncpy below only copies 4 bytes, and the reader checks all 5. */ + /* The coherency record is written below at fixed byte offsets; the version + * byte at offset 74 is the highest, so the page must be at least 75 bytes. + * Lock that invariant at compile time so a future change to the page-size + * constants cannot silently overflow this stack buffer. */ + _Static_assert(sizeof(coh_data) > 74, + "MAM coherency page too small for the coherency record layout"); + + /* Zero unwritten bytes for deterministic on-medium content. The "LTFS" + * signature's NUL terminator is written explicitly below, so correctness of + * the signature no longer depends on this memset. */ memset(coh_data, 0, sizeof(coh_data)); CHECK_ARG_NULL(dev, -LTFS_NULL_ARG); @@ -1786,8 +1795,13 @@ int tape_set_cart_coherency(struct device_data *dev, const tape_partition_t part /* APPLICATION CLIENT SPECIFIC INFORMATION LENGTH */ coh_data[30] = 0; /* Size of APPLICATION CLIENT SPECIFIC INFORMATION (Byte 1) */ coh_data[31] = 43; /* Size of APPLICATION CLIENT SPECIFIC INFORMATION (Byte 0) */ - /* Size of the buffer to insert 'LTFS' needs to be size of 5 for the 4 letters and the null terminator*/ - arch_strncpy((char *)coh_data + 32,"LTFS", 5, 4); + /* Volume coherency signature: the 4 letters "LTFS" plus a trailing NUL at + * offset 36. The reader compares all 5 bytes, so copy the NUL terminator + * explicitly (an earlier strncpy-based write copied only 4 bytes and left + * byte 36 uninitialised, triggering a full consistency check on every mount). */ + _Static_assert(sizeof(TC_MAM_COHERENCY_SIGNATURE) == 5, + "LTFS coherency signature must be 4 characters plus a NUL terminator"); + memcpy(coh_data + 32, TC_MAM_COHERENCY_SIGNATURE, sizeof(TC_MAM_COHERENCY_SIGNATURE)); memcpy(coh_data + 37, coh->uuid, 37); /* Version field diff --git a/src/libltfs/tape_ops.h b/src/libltfs/tape_ops.h index 34c7a128..75160422 100644 --- a/src/libltfs/tape_ops.h +++ b/src/libltfs/tape_ops.h @@ -250,6 +250,7 @@ typedef enum { #define TC_MAM_PAGE_VCR_SIZE (0x4) /* Size of Volume Change Reference */ #define TC_MAM_PAGE_COHERENCY (0x080C) #define TC_MAM_PAGE_COHERENCY_SIZE (0x46) +#define TC_MAM_COHERENCY_SIGNATURE "LTFS" /* Volume coherency signature; 4 chars + NUL, reader compares all 5 */ #define TC_MAM_APP_VENDER (0x0800) #define TC_MAM_APP_VENDER_SIZE (0x8) From 2d1490a8f6f87480f25c2e256b071ebde4d7f03a Mon Sep 17 00:00:00 2001 From: Hugo Hurskainen Date: Mon, 15 Jun 2026 00:11:25 +0300 Subject: [PATCH 3/3] refactor: guard sibling MAM page buffers with _Static_assert Apply the same compile-time bounds check used for the coherency page to the other two fixed-offset MAM buffers in tape.c: - tape_get_volume_change_reference reads a 32-bit VCR at offset 5, so assert the page holds at least 5 + sizeof(uint32_t) bytes. - tape_get_cart_volume_lock_status reads the status byte at offset TC_MAM_PAGE_HEADER_SIZE, so assert the page extends past the header. Both buffer sizes derive from page-size constants in tape_ops.h; the asserts fail the build if those constants are ever reduced below what the fixed-offset reads require, instead of silently reading past the end of the stack buffer. --- src/libltfs/tape.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/libltfs/tape.c b/src/libltfs/tape.c index 944ee5b9..a3280fd6 100644 --- a/src/libltfs/tape.c +++ b/src/libltfs/tape.c @@ -1665,6 +1665,13 @@ int tape_get_volume_change_reference(struct device_data *dev, uint64_t *volume_c int ret; unsigned char vcr_data[TC_MAM_PAGE_VCR_SIZE + TC_MAM_PAGE_HEADER_SIZE]; + /* The VCR is read as a 32-bit field at offset 5 (just past the page + * header), so the buffer must hold at least 5 + 4 bytes. Lock that at + * compile time so a change to the page-size constants cannot make the + * read below run off the end of the buffer. */ + _Static_assert(sizeof(vcr_data) >= 5 + sizeof(uint32_t), + "MAM VCR page too small to hold the volume change reference"); + CHECK_ARG_NULL(dev, -LTFS_NULL_ARG); CHECK_ARG_NULL(dev->backend, -LTFS_NULL_ARG); @@ -1821,6 +1828,13 @@ int tape_get_cart_volume_lock_status(struct device_data *dev, int *status) int ret; unsigned char attr_data[TC_MAM_LOCKED_MAM_SIZE + TC_MAM_PAGE_HEADER_SIZE]; + /* The lock status byte is read at offset TC_MAM_PAGE_HEADER_SIZE, so the + * buffer must extend past the page header. Lock that at compile time so a + * change to the page-size constants cannot make the read below run off the + * end of the buffer. */ + _Static_assert(sizeof(attr_data) > TC_MAM_PAGE_HEADER_SIZE, + "MAM locked-MAM page too small to hold the lock status byte"); + CHECK_ARG_NULL(dev, -LTFS_NULL_ARG); CHECK_ARG_NULL(status, -LTFS_NULL_ARG);