From 68b96608d0ef840ecb907080c1281cbbfee3f39e Mon Sep 17 00:00:00 2001 From: Hugo Hurskainen Date: Tue, 9 Jun 2026 18:02:06 +0300 Subject: [PATCH 1/7] 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/7] 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/7] 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); From 37489c64fa29035d3d663fb33789c0ce5d530c08 Mon Sep 17 00:00:00 2001 From: Hugo Hurskainen Date: Thu, 6 Aug 2026 21:45:08 +0300 Subject: [PATCH 4/7] refactor: remove redundant MAM buffer static assertions The _Static_asserts added while hardening the MAM coherency handling restated the buffer declarations a few lines above and reintroduced magic numbers (74, 5) that duplicate the fixed offsets. They add no real protection -- a page-layout change would require rewriting the offset writes regardless -- so drop them per review feedback. The NUL-terminator fix, the explicit "LTFS" signature memcpy, and the TC_MAM_COHERENCY_SIGNATURE definition are retained. --- src/libltfs/tape.c | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/libltfs/tape.c b/src/libltfs/tape.c index 4534b512..95d6a310 100644 --- a/src/libltfs/tape.c +++ b/src/libltfs/tape.c @@ -1665,13 +1665,6 @@ 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); @@ -1777,13 +1770,6 @@ 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]; - /* 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. */ @@ -1806,8 +1792,6 @@ int tape_set_cart_coherency(struct device_data *dev, const tape_partition_t part * 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); /* @@ -1828,13 +1812,6 @@ 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); From 10ef71994c90a6a4a755b6d79d56c6108dcddbc6 Mon Sep 17 00:00:00 2001 From: Hugo Hurskainen Date: Thu, 6 Aug 2026 21:57:08 +0300 Subject: [PATCH 5/7] refactor: name the MAM coherency page field offsets Replace the hardcoded byte offsets in the volume coherency read/write paths (and the shared MAM attribute header offsets they use) with named TC_MAM_* defines in tape_ops.h, per review feedback about magic numbers. The coherency writer and reader, the VCR reader, and the lock-status reader now index their buffers by name, and the UUID copy uses sizeof(coh->uuid). Pure refactor -- the on-medium bytes are unchanged; the one non-trivial rewrite (the application-client-specific-information length, previously two byte writes, now a single ltfs_u16tobe) was verified byte-identical. --- src/libltfs/tape.c | 64 +++++++++++++++++++++--------------------- src/libltfs/tape_ops.h | 20 +++++++++++++ 2 files changed, 52 insertions(+), 32 deletions(-) diff --git a/src/libltfs/tape.c b/src/libltfs/tape.c index 95d6a310..2ab2dc45 100644 --- a/src/libltfs/tape.c +++ b/src/libltfs/tape.c @@ -1674,7 +1674,7 @@ int tape_get_volume_change_reference(struct device_data *dev, uint64_t *volume_c sizeof(vcr_data)); if (ret == 0) { - *volume_change_ref = (uint64_t)ltfs_betou32(vcr_data + 5); + *volume_change_ref = (uint64_t)ltfs_betou32(vcr_data + TC_MAM_PAGE_HEADER_SIZE); if (*volume_change_ref == UINT32_MAX) *volume_change_ref = UINT64_MAX; /* maintain "unusable VCR" state correctly */ } else { @@ -1705,9 +1705,9 @@ int tape_get_cart_coherency(struct device_data *dev, const tape_partition_t part sizeof(coh_data)); if (ret == 0) { - uint16_t id = ltfs_betou16(coh_data); - uint16_t len = ltfs_betou16(coh_data + 3); - uint8_t vcr_size = coh_data[5]; + uint16_t id = ltfs_betou16(coh_data + TC_MAM_ATTR_ID_OFFSET); + uint16_t len = ltfs_betou16(coh_data + TC_MAM_ATTR_LENGTH_OFFSET); + uint8_t vcr_size = coh_data[TC_MAM_COH_VCR_SIZE_OFFSET]; if (id != TC_MAM_PAGE_COHERENCY) { ltfsmsg(LTFS_WARN, 12058W, id); @@ -1723,36 +1723,37 @@ int tape_get_cart_coherency(struct device_data *dev, const tape_partition_t part coh->set_id = 0; switch (vcr_size) { - case 8: - coh->volume_change_ref = ltfs_betou64(coh_data + 6); + case TC_MAM_COH_VCR_LENGTH: + coh->volume_change_ref = ltfs_betou64(coh_data + TC_MAM_COH_VCR_OFFSET); break; default: ltfsmsg(LTFS_WARN, 12060W, vcr_size); return -LTFS_UNEXPECTED_VALUE; } - coh->count = ltfs_betou64(coh_data + 14); - coh->set_id = ltfs_betou64(coh_data + 22); + coh->count = ltfs_betou64(coh_data + TC_MAM_COH_COUNT_OFFSET); + coh->set_id = ltfs_betou64(coh_data + TC_MAM_COH_SETID_OFFSET); /* Allow ap_clent_specific_len is 42 and 43 to keep backward compatibility. * It should be 43 but in LTFS 1.0 and 1.0.1, it was set 42 as a code bug... */ - uint16_t ap_clent_specific_len = ltfs_betou16(coh_data + 30); - if (ap_clent_specific_len != 42 && ap_clent_specific_len != 43) { + uint16_t ap_clent_specific_len = ltfs_betou16(coh_data + TC_MAM_COH_APPINFO_LEN_OFFSET); + if (ap_clent_specific_len != TC_MAM_COH_APPINFO_LENGTH_LEGACY && + ap_clent_specific_len != TC_MAM_COH_APPINFO_LENGTH) { ltfsmsg(LTFS_WARN, 12061W, ap_clent_specific_len); return -LTFS_UNEXPECTED_VALUE; - } else if (strncmp((char *)coh_data + 32, TC_MAM_COHERENCY_SIGNATURE, + } else if (strncmp((char *)coh_data + TC_MAM_COH_SIGNATURE_OFFSET, TC_MAM_COHERENCY_SIGNATURE, sizeof(TC_MAM_COHERENCY_SIGNATURE)) != 0) { ltfsmsg(LTFS_WARN, 12062W); return -LTFS_UNEXPECTED_VALUE; } - memcpy(coh->uuid, coh_data + 37, 37); + memcpy(coh->uuid, coh_data + TC_MAM_COH_UUID_OFFSET, sizeof(coh->uuid)); /* Don't need to check the version field because the values parsed above are guaranteed * to be supported in every version of the LTFS MAM parameters. */ - coh->version = coh_data[74]; + coh->version = coh_data[TC_MAM_COH_VERSION_OFFSET]; } else ltfsmsg(LTFS_WARN, 12057W, ret); @@ -1778,28 +1779,27 @@ int tape_set_cart_coherency(struct device_data *dev, const tape_partition_t part CHECK_ARG_NULL(dev, -LTFS_NULL_ARG); CHECK_ARG_NULL(dev->backend, -LTFS_NULL_ARG); - ltfs_u16tobe(coh_data, TC_MAM_PAGE_COHERENCY); - coh_data[2] = 0; - ltfs_u16tobe(coh_data + 3, TC_MAM_PAGE_COHERENCY_SIZE); - coh_data[5] = 0x08; /* Size of Volume Change Reference Value (VCR)*/ - ltfs_u64tobe(coh_data + 6, coh->volume_change_ref); - ltfs_u64tobe(coh_data + 14, coh->count); /* VOLUME COHERENCY COUNT */ - ltfs_u64tobe(coh_data + 22, coh->set_id); /* VOLUME COHERENCY SET IDENTIFIER */ - /* 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) */ - /* 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). */ - memcpy(coh_data + 32, TC_MAM_COHERENCY_SIGNATURE, sizeof(TC_MAM_COHERENCY_SIGNATURE)); - memcpy(coh_data + 37, coh->uuid, 37); + ltfs_u16tobe(coh_data + TC_MAM_ATTR_ID_OFFSET, TC_MAM_PAGE_COHERENCY); + coh_data[TC_MAM_ATTR_FORMAT_OFFSET] = 0; + ltfs_u16tobe(coh_data + TC_MAM_ATTR_LENGTH_OFFSET, TC_MAM_PAGE_COHERENCY_SIZE); + coh_data[TC_MAM_COH_VCR_SIZE_OFFSET] = TC_MAM_COH_VCR_LENGTH; /* Size of Volume Change Reference (VCR) */ + ltfs_u64tobe(coh_data + TC_MAM_COH_VCR_OFFSET, coh->volume_change_ref); + ltfs_u64tobe(coh_data + TC_MAM_COH_COUNT_OFFSET, coh->count); /* VOLUME COHERENCY COUNT */ + ltfs_u64tobe(coh_data + TC_MAM_COH_SETID_OFFSET, coh->set_id); /* VOLUME COHERENCY SET IDENTIFIER */ + /* APPLICATION CLIENT SPECIFIC INFORMATION LENGTH: signature + UUID + version */ + ltfs_u16tobe(coh_data + TC_MAM_COH_APPINFO_LEN_OFFSET, TC_MAM_COH_APPINFO_LENGTH); + /* Volume coherency signature: the 4 letters "LTFS" plus a trailing NUL. The + * reader compares all 5 bytes, so copy the NUL terminator explicitly (an + * earlier strncpy-based write copied only 4 bytes and left the terminator + * byte uninitialised, triggering a full consistency check on every mount). */ + memcpy(coh_data + TC_MAM_COH_SIGNATURE_OFFSET, TC_MAM_COHERENCY_SIGNATURE, sizeof(TC_MAM_COHERENCY_SIGNATURE)); + memcpy(coh_data + TC_MAM_COH_UUID_OFFSET, coh->uuid, sizeof(coh->uuid)); /* Version field 0: GA and PGA1 1: From PGA2 */ - coh_data[74] = coh->version; /* version field should be specified before calling this function */ + coh_data[TC_MAM_COH_VERSION_OFFSET] = coh->version; /* version field should be specified before calling this function */ ret = dev->backend->write_attribute(dev->backend_data, part, coh_data, sizeof(coh_data)); if (ret < 0) @@ -1822,8 +1822,8 @@ int tape_get_cart_volume_lock_status(struct device_data *dev, int *status) sizeof(attr_data)); if (ret == 0) { - uint16_t id = ltfs_betou16(attr_data); - uint16_t len = ltfs_betou16(attr_data + 3); + uint16_t id = ltfs_betou16(attr_data + TC_MAM_ATTR_ID_OFFSET); + uint16_t len = ltfs_betou16(attr_data + TC_MAM_ATTR_LENGTH_OFFSET); if (id != TC_MAM_LOCKED_MAM) { ltfsmsg(LTFS_WARN, 17196W, id); diff --git a/src/libltfs/tape_ops.h b/src/libltfs/tape_ops.h index 75160422..ec62f76b 100644 --- a/src/libltfs/tape_ops.h +++ b/src/libltfs/tape_ops.h @@ -252,6 +252,26 @@ typedef enum { #define TC_MAM_PAGE_COHERENCY_SIZE (0x46) #define TC_MAM_COHERENCY_SIGNATURE "LTFS" /* Volume coherency signature; 4 chars + NUL, reader compares all 5 */ +/* Byte offsets of the common MAM attribute header fields. The attribute value + * begins at TC_MAM_PAGE_HEADER_SIZE. */ +#define TC_MAM_ATTR_ID_OFFSET (0) /* 2-byte attribute identifier */ +#define TC_MAM_ATTR_FORMAT_OFFSET (2) /* 1-byte format byte */ +#define TC_MAM_ATTR_LENGTH_OFFSET (3) /* 2-byte attribute length */ + +/* Byte offsets of the fields within the volume coherency attribute + * (TC_MAM_PAGE_COHERENCY), measured from the start of the attribute. */ +#define TC_MAM_COH_VCR_SIZE_OFFSET (5) /* 1-byte length of the VCR field */ +#define TC_MAM_COH_VCR_OFFSET (6) /* 8-byte volume change reference */ +#define TC_MAM_COH_COUNT_OFFSET (14) /* 8-byte coherency count */ +#define TC_MAM_COH_SETID_OFFSET (22) /* 8-byte coherency set identifier */ +#define TC_MAM_COH_APPINFO_LEN_OFFSET (30) /* 2-byte app client specific info length */ +#define TC_MAM_COH_SIGNATURE_OFFSET (32) /* 5-byte "LTFS\0" signature */ +#define TC_MAM_COH_UUID_OFFSET (37) /* volume UUID (sizeof tc_coherency.uuid) */ +#define TC_MAM_COH_VERSION_OFFSET (74) /* 1-byte format version */ +#define TC_MAM_COH_VCR_LENGTH (8) /* value stored at the VCR size field */ +#define TC_MAM_COH_APPINFO_LENGTH (43) /* signature + UUID + version byte count */ +#define TC_MAM_COH_APPINFO_LENGTH_LEGACY (42) /* LTFS 1.0/1.0.1 wrote 42 (off-by-one) */ + #define TC_MAM_APP_VENDER (0x0800) #define TC_MAM_APP_VENDER_SIZE (0x8) #define TC_MAM_APP_NAME (0x0801) From da49d740d39af26a19b139ccbd11fe292e0d6f70 Mon Sep 17 00:00:00 2001 From: Hugo Hurskainen Date: Sat, 8 Aug 2026 12:42:16 +0300 Subject: [PATCH 6/7] refactor: cleaning up ai slop comments --- src/libltfs/tape.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/libltfs/tape.c b/src/libltfs/tape.c index 2ab2dc45..3f6cff14 100644 --- a/src/libltfs/tape.c +++ b/src/libltfs/tape.c @@ -1771,9 +1771,6 @@ 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 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); @@ -1788,10 +1785,7 @@ int tape_set_cart_coherency(struct device_data *dev, const tape_partition_t part ltfs_u64tobe(coh_data + TC_MAM_COH_SETID_OFFSET, coh->set_id); /* VOLUME COHERENCY SET IDENTIFIER */ /* APPLICATION CLIENT SPECIFIC INFORMATION LENGTH: signature + UUID + version */ ltfs_u16tobe(coh_data + TC_MAM_COH_APPINFO_LEN_OFFSET, TC_MAM_COH_APPINFO_LENGTH); - /* Volume coherency signature: the 4 letters "LTFS" plus a trailing NUL. The - * reader compares all 5 bytes, so copy the NUL terminator explicitly (an - * earlier strncpy-based write copied only 4 bytes and left the terminator - * byte uninitialised, triggering a full consistency check on every mount). */ + /* memcpy instead of arch_strncpy to deterministically copy all bytes of the coherency signature including NULL terminator */ memcpy(coh_data + TC_MAM_COH_SIGNATURE_OFFSET, TC_MAM_COHERENCY_SIGNATURE, sizeof(TC_MAM_COHERENCY_SIGNATURE)); memcpy(coh_data + TC_MAM_COH_UUID_OFFSET, coh->uuid, sizeof(coh->uuid)); /* From c64a167ec231bae77a2b1e1c59847ab8bf0db041 Mon Sep 17 00:00:00 2001 From: Hugo Hurskainen Date: Sat, 8 Aug 2026 12:46:40 +0300 Subject: [PATCH 7/7] refactor: remove unnecessary memset Writes to the coh_data are now fully deteministic using the memcpy so no need to first zero the buffer. The memset's original job, supplying the "LTFS" signature's NUL terminator, is now done by the explicit memcpy. --- src/libltfs/tape.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libltfs/tape.c b/src/libltfs/tape.c index 3f6cff14..f2d1fc7b 100644 --- a/src/libltfs/tape.c +++ b/src/libltfs/tape.c @@ -1771,8 +1771,6 @@ 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]; - memset(coh_data, 0, sizeof(coh_data)); - CHECK_ARG_NULL(dev, -LTFS_NULL_ARG); CHECK_ARG_NULL(dev->backend, -LTFS_NULL_ARG);