diff --git a/SPECS/sleuthkit/CVE-2026-40024.patch b/SPECS/sleuthkit/CVE-2026-40024.patch new file mode 100644 index 00000000000..bb28352e07f --- /dev/null +++ b/SPECS/sleuthkit/CVE-2026-40024.patch @@ -0,0 +1,129 @@ +From a3f96b3bc36a8bb1a00c297f77110d4a6e7dd31b Mon Sep 17 00:00:00 2001 +From: Brian Carrier +Date: Sat, 28 Feb 2026 16:36:48 -0500 +Subject: [PATCH] Clean up any path traversal symbols in export path. Reported + by Mobasi + +Upstream Patch reference: https://github.com/sleuthkit/sleuthkit/commit/a3f96b3bc36a8bb1a00c297f77110d4a6e7dd31b.patch + +--- + tools/autotools/tsk_recover.cpp | 53 ++++++++++++++++++++++++++++----- + 1 file changed, 46 insertions(+), 7 deletions(-) + +diff --git a/tools/autotools/tsk_recover.cpp b/tools/autotools/tsk_recover.cpp +index 2323545..73ffaa8 100755 +--- a/tools/autotools/tsk_recover.cpp ++++ b/tools/autotools/tsk_recover.cpp +@@ -46,6 +46,17 @@ usage() + exit(1); + } + ++ ++// special characters we do not want to have in the name when writing out. ++#ifdef TSK_WIN32 ++#define TSK_IS_SPL_FILE_CHAR(x) \ ++ (((x) == 0x3A) || ((x) == 0x5C)) ++#else ++#define TSK_IS_SPL_FILE_CHAR(x) \ ++ ((x) == 0x2F) ++#endif ++ ++ + #ifdef TSK_WIN32 + #include + #include "shlobj.h" +@@ -136,8 +147,20 @@ uint8_t TskRecover::writeFile(TSK_FS_FILE * a_fs_file, const char *a_path) + + // clean up any control characters + for (size_t i = 0; i < ilen; i++) { +- if (TSK_IS_CNTRL(path8[i])) ++ if (TSK_IS_CNTRL(path8[i])) { + path8[i] = '^'; ++ } ++ ++ if (path8[i] == '/') ++ path8[i] = '\\'; ++ ++ // make sure there is no \..\ path traversal ++ if (i + 4 < ilen) { ++ if ((path8[i] == '\\') && (path8[i+1] == '.') && (path8[i+2] == '.') && (path8[i+3] == '\\')) { ++ path8[i+1] = '^'; ++ path8[i+2] = '^'; ++ } ++ } + } + + //convert path from utf8 to utf16 +@@ -169,6 +192,8 @@ uint8_t TskRecover::writeFile(TSK_FS_FILE * a_fs_file, const char *a_path) + for (size_t i = 0; i < len; i++) { + if (path16full[i] == L'/') + path16full[i] = L'\\'; ++ ++ // break at path seperator to make that directory + if (((i > 0) && (path16full[i] == L'\\') && (path16full[i - 1] != L'\\')) + || ((path16full[i] != L'\\') && (i == len - 1))) { + uint8_t +@@ -198,8 +223,10 @@ uint8_t TskRecover::writeFile(TSK_FS_FILE * a_fs_file, const char *a_path) + char name8[FILENAME_MAX]; + strncpy(name8, a_fs_file->name->name, FILENAME_MAX); + for (int i = 0; name8[i] != '\0'; i++) { +- if (TSK_IS_CNTRL(name8[i])) ++ //make sure there is no slash, which could lead to path traversal ++ if (TSK_IS_CNTRL(name8[i]) || TSK_IS_SPL_FILE_CHAR(name8[i])) { + name8[i] = '^'; ++ } + } + + //convert file name from utf8 to utf16 +@@ -257,13 +284,23 @@ uint8_t TskRecover::writeFile(TSK_FS_FILE * a_fs_file, const char *a_path) + for (size_t i = 0; i < strlen(fbuf); i++) { + if (TSK_IS_CNTRL(fbuf[i])) + fbuf[i] = '^'; ++ ++ // make sure there is no /../ path traversal ++ if (i + 4 < strlen(fbuf)) { ++ if ((fbuf[i] == '/') && (fbuf[i+1] == '.') && (fbuf[i+2] == '.') && (fbuf[i+3] == '/')) { ++ fbuf[i+1] = '^'; ++ fbuf[i+2] = '^'; ++ } ++ } + } + + // see if the directory already exists. Create, if not. ++ // and all of the missing layers + if (0 != lstat(fbuf, &statds)) { + size_t + len = strlen(fbuf); + for (size_t i = 0; i < len; i++) { ++ // stop if we are at a path separator to make that folder + if (((i > 0) && (fbuf[i] == '/') && (fbuf[i - 1] != '/')) + || ((fbuf[i] != '/') && (i == len - 1))) { + uint8_t +@@ -288,17 +325,19 @@ uint8_t TskRecover::writeFile(TSK_FS_FILE * a_fs_file, const char *a_path) + } + + if (fbuf[strlen(fbuf) - 1] != '/') +- strncat(fbuf, "/", PATH_MAX - strlen(fbuf)); ++ strncat(fbuf, "/", PATH_MAX - strlen(fbuf)-1); + +- strncat(fbuf, a_fs_file->name->name, PATH_MAX - strlen(fbuf)); ++ int nstart = strlen(fbuf); ++ strncat(fbuf, a_fs_file->name->name, PATH_MAX - strlen(fbuf)-1); + + //do name mangling of the file name that was just added +- for (int i = strlen(fbuf)-1; fbuf[i] != '/'; i--) { +- if (TSK_IS_CNTRL(fbuf[i])) ++ for (int i = nstart; fbuf[i] != '\0'; i++) { ++ // need to make sure it doesn't have any slashes in it, which could lead ++ // to path traversal ++ if (TSK_IS_CNTRL(fbuf[i]) || TSK_IS_SPL_FILE_CHAR(fbuf[i])) + fbuf[i] = '^'; + } + +- + // open the file + if ((hFile = fopen(fbuf, "w+")) == NULL) { + fprintf(stderr, "Error opening file for writing (%s)\n", fbuf); +-- +2.43.0 + diff --git a/SPECS/sleuthkit/CVE-2026-40025.patch b/SPECS/sleuthkit/CVE-2026-40025.patch new file mode 100644 index 00000000000..57cdfc16e06 --- /dev/null +++ b/SPECS/sleuthkit/CVE-2026-40025.patch @@ -0,0 +1,275 @@ +From db22d70f39b0b50704378172f81603a0334a5733 Mon Sep 17 00:00:00 2001 +From: AllSpark +Date: Sat, 11 Apr 2026 16:30:00 +0000 +Subject: [PATCH] APFS: Fix bounds overrun in wrapped_key_parser and carry + sizes in key data to prevent overrun; adjust APFSKeybag get_key and callers; + add APFS_sized_key_data. Reported by Mobasi. + +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: AI Backport of https://github.com/sleuthkit/sleuthkit/commit/8b9c9e7d493bd68624f3b1a3963edd45c3ff7611.patch + +--- + tsk/fs/apfs.cpp | 73 ++++++++++++++++++++++++++------------------- + tsk/fs/tsk_apfs.hpp | 22 ++++++++++++-- + 2 files changed, 62 insertions(+), 33 deletions(-) + +diff --git a/tsk/fs/apfs.cpp b/tsk/fs/apfs.cpp +index f0a8bad..33f43a3 100644 +--- a/tsk/fs/apfs.cpp ++++ b/tsk/fs/apfs.cpp +@@ -42,22 +42,24 @@ static __forceinline int lsbset(long x) { + #endif // _MSC_VER + + class wrapped_key_parser { +- // TODO(JTS): This code assume a well-formed input. It needs some sanity +- // checking! +- + using tag = uint8_t; + using view = span; + + const uint8_t* _data; ++ const uint8_t* _end; // one-past-the-end of the buffer + +- size_t get_length(const uint8_t** pos) const noexcept { ++ // Returns true and leaves *pos unchanged on any bounds violation. ++ bool is_eob(const uint8_t** pos, size_t* out_len) const noexcept { + auto data = *pos; + ++ if (data >= _end) return true; + size_t len = *data++; + + if (len & 0x80) { ++ size_t enc_len = len & 0x7F; + len = 0; +- auto enc_len = len & 0x7F; ++ if (enc_len == 0 || static_cast(_end - data) < enc_len) ++ return true; + while (enc_len--) { + len <<= 8; + len |= *data++; +@@ -65,15 +67,23 @@ class wrapped_key_parser { + } + + *pos = data; +- return len; ++ *out_len = len; ++ return false; + } + ++ // Returns an invalid (empty) view if the tag is not found or a bounds ++ // violation is detected. + const view get_tag(tag t) const noexcept { + auto data = _data; + +- while (true) { ++ while (data < _end) { + const auto tag = *data++; +- const auto len = get_length(&data); ++ ++ size_t len = 0; ++ if (is_eob(&data, &len)) break; ++ ++ // Ensure the value bytes are within the buffer. ++ if (static_cast(_end - data) < len) break; + + if (tag == t) { + return {data, len}; +@@ -81,6 +91,9 @@ class wrapped_key_parser { + + data += len; + } ++ ++ // Tag not found or buffer overrun — return an invalid view. ++ return {}; + } + + // Needed for the recursive variadic to compile, but should never be +@@ -90,7 +103,9 @@ class wrapped_key_parser { + } + + public: +- wrapped_key_parser(const void* data) noexcept : _data{(const uint8_t*)data} {} ++ wrapped_key_parser(const void* data, size_t size) noexcept ++ : _data{static_cast(data)}, ++ _end{static_cast(data) + size} {} + + template + const view get_data(tag t, Args... args) const noexcept { +@@ -100,7 +115,8 @@ class wrapped_key_parser { + return data; + } + +- return wrapped_key_parser{data.data()}.get_data(args...); ++ // Recurse into the nested TLV value; its buffer is exactly `data`. ++ return wrapped_key_parser{data.data(), data.count()}.get_data(args...); + } + + template +@@ -337,10 +353,10 @@ APFSFileSystem::APFSFileSystem(const APFSPool& pool, + } + + APFSFileSystem::wrapped_kek::wrapped_kek(Guid&& id, +- const std::unique_ptr& kp) ++ const APFS_sized_key_data& kp) + : uuid{std::forward(id)} { + // Parse KEK +- wrapped_key_parser wp{kp.get()}; ++ wrapped_key_parser wp{kp.get(), kp.size}; + + // Get flags + flags = wp.get_number(0x30, 0xA3, 0x82); +@@ -389,12 +405,12 @@ void APFSFileSystem::init_crypto_info() { + const auto container_kb = _pool.nx()->keybag(); + + auto data = container_kb.get_key(uuid(), APFS_KB_TYPE_VOLUME_KEY); +- if (data == nullptr) { ++ if (!data) { + throw std::runtime_error( + "APFSFileSystem: can not find volume encryption key"); + } + +- wrapped_key_parser wp{ data.get() }; ++ wrapped_key_parser wp{ data.get(), data.size }; + + // Get Wrapped VEK + auto kek_data = wp.get_data(0x30, 0xA3, 0x83); +@@ -415,7 +431,7 @@ void APFSFileSystem::init_crypto_info() { + std::memcpy(_crypto.vek_uuid, kek_data.data(), sizeof(_crypto.vek_uuid)); + + data = container_kb.get_key(uuid(), APFS_KB_TYPE_UNLOCK_RECORDS); +- if (data == nullptr) { ++ if (!data) { + throw std::runtime_error( + "APFSFileSystem: can not find volume recovery key"); + } +@@ -434,7 +450,7 @@ void APFSFileSystem::init_crypto_info() { + + data = recs.get_key(uuid(), APFS_KB_TYPE_PASSPHRASE_HINT); + +- if (data != nullptr) { ++ if (data) { + _crypto.password_hint = std::string((const char*)data.get()); + } + +@@ -991,10 +1007,10 @@ APFSKeybag::APFSKeybag(const APFSPool& pool, const apfs_block_num block_num, + } + } + +-std::unique_ptr APFSKeybag::get_key(const Guid& uuid, +- uint16_t type) const { ++APFS_sized_key_data APFSKeybag::get_key(const Guid& uuid, ++ uint16_t type) const { + if (kb()->num_entries == 0) { +- return nullptr; ++ return {}; + } + + // First key is immediately after the header +@@ -1003,20 +1019,17 @@ std::unique_ptr APFSKeybag::get_key(const Guid& uuid, + for (auto i = 0U; i < kb()->num_entries; i++) { + if (next_key->type == type && + std::memcmp(next_key->uuid, uuid.bytes().data(), 16) == 0) { +- // We've found a matching key. Copy it's data to a pointer and return it. ++ // We've found a matching key. Copy its data to a pointer and return it. + const auto data = reinterpret_cast(next_key + 1); + +- // We're padding the data with an extra byte so we can null-terminate +- // any data strings. There might be a better way. ++ // +1 byte for null-terminator guard on string values + auto dp = std::make_unique(next_key->length + 1); +- + std::memcpy(dp.get(), data, next_key->length); + +- return dp; ++ return {std::move(dp), next_key->length}; + } + + // Calculate address of next key (ensuring alignment) +- + const auto nk_addr = + (uintptr_t)next_key + + ((sizeof(*next_key) + next_key->length + 0x0F) & ~0x0FULL); +@@ -1025,7 +1038,7 @@ std::unique_ptr APFSKeybag::get_key(const Guid& uuid, + } + + // Not Found +- return nullptr; ++ return {}; + } + + std::vector APFSKeybag::get_keys() const { +@@ -1037,13 +1050,13 @@ std::vector APFSKeybag::get_keys() const { + for (auto i = 0U; i < kb()->num_entries; i++) { + const auto data = reinterpret_cast(next_key + 1); + +- // We're padding the data with an extra byte so we can null-terminate +- // any data strings. There might be a better way. ++ // +1 byte for null-terminator guard on string values + auto dp = std::make_unique(next_key->length + 1); +- + std::memcpy(dp.get(), data, next_key->length); + +- keys.emplace_back(key{{next_key->uuid}, std::move(dp), next_key->type}); ++ keys.emplace_back(key{{next_key->uuid}, ++ APFS_sized_key_data{std::move(dp), next_key->length}, ++ next_key->type}); + + // Calculate address of next key (ensuring alignment) + const auto nk_addr = +diff --git a/tsk/fs/tsk_apfs.hpp b/tsk/fs/tsk_apfs.hpp +index 6bdc976..f475c79 100755 +--- a/tsk/fs/tsk_apfs.hpp ++++ b/tsk/fs/tsk_apfs.hpp +@@ -37,6 +37,22 @@ constexpr T bitfield_value(T bitfield, int bits, int shift) noexcept { + + class APFSPool; + ++// An owning buffer that also carries its own length, so callers never need to ++// track the size separately. Drop-in replacement for unique_ptr ++// at call sites — supports operator bool() and .get() for compatibility. ++struct APFS_sized_key_data { ++ std::unique_ptr ptr; ++ size_t size{0}; ++ ++ // Allows `if (data)` / `if (!data)` checks to keep working. ++ explicit operator bool() const noexcept { return ptr != nullptr; } ++ ++ // Mimic unique_ptr's .get() so existing call sites need minimal changes. ++ const uint8_t* get() const noexcept { return ptr.get(); } ++}; ++ ++ ++ + class APFSObject : public APFSBlock { + protected: + inline const apfs_obj_header *obj() const noexcept { +@@ -829,7 +845,7 @@ class APFSKeybag : public APFSObject { + + using key = struct { + Guid uuid; +- std::unique_ptr data; ++ APFS_sized_key_data data; + uint16_t type; + }; + +@@ -837,7 +853,7 @@ class APFSKeybag : public APFSObject { + APFSKeybag(const APFSPool &pool, const apfs_block_num block_num, + const uint8_t *key, const uint8_t *key2 = nullptr); + +- std::unique_ptr get_key(const Guid &uuid, uint16_t type) const; ++ APFS_sized_key_data get_key(const Guid &uuid, uint16_t type) const; + + std::vector get_keys() const; + }; +@@ -966,7 +982,7 @@ class APFSFileSystem : public APFSObject { + uint64_t iterations; + uint64_t flags; + uint8_t salt[0x10]; +- wrapped_kek(Guid &&uuid, const std::unique_ptr &); ++ wrapped_kek(Guid &&uuid, const APFS_sized_key_data &); + + inline bool hw_crypt() const noexcept { + // If this bit is set, some sort of hardware encryption is used. +-- +2.43.0 + diff --git a/SPECS/sleuthkit/CVE-2026-40026.patch b/SPECS/sleuthkit/CVE-2026-40026.patch new file mode 100644 index 00000000000..4034d7f8678 --- /dev/null +++ b/SPECS/sleuthkit/CVE-2026-40026.patch @@ -0,0 +1,67 @@ +From a95b0ac21733b059a517aaefa667a17e1bcbdee1 Mon Sep 17 00:00:00 2001 +From: Brian Carrier +Date: Sun, 1 Mar 2026 12:30:57 -0500 +Subject: [PATCH] fix bounds checks. Reported by Mobasi + +Upstream Patch reference: https://github.com/sleuthkit/sleuthkit/commit/a95b0ac21733b059a517aaefa667a17e1bcbdee1.patch + +--- + tsk/fs/iso9660.c | 31 +++++++++++++++++++------------ + 1 file changed, 19 insertions(+), 12 deletions(-) + +diff --git a/tsk/fs/iso9660.c b/tsk/fs/iso9660.c +index 779d1cf..a129a01 100755 +--- a/tsk/fs/iso9660.c ++++ b/tsk/fs/iso9660.c +@@ -112,7 +112,7 @@ parse_susp(TSK_FS_INFO * fs, char *buf, int count, FILE * hFile) + while ((uintptr_t)buf + sizeof(iso9660_susp_head) <= (uintptr_t)end) { + iso9660_susp_head *head = (iso9660_susp_head *) buf; + +- if (buf + head->len - 1 > end) ++ if ((buf + head->len - 1 > end) || (head->len == 0)) + break; + + /* Identify the entry type -- listed in the order +@@ -209,21 +209,28 @@ parse_susp(TSK_FS_INFO * fs, char *buf, int count, FILE * hFile) + else if ((head->sig[0] == 'E') && (head->sig[1] == 'R')) { + iso9660_susp_er *er = (iso9660_susp_er *) buf; + if (hFile) { +- char buf[258]; ++ char buf2[258]; + fprintf(hFile, "ER Entry\n"); + +- memcpy(buf, er->ext_id, er->len_id); +- buf[er->len_id] = '\0'; +- fprintf(hFile, "* Extension ID: %s\n", buf); ++ if ((er->len_id < 256) && (er->ext_id + er->len_id < (uintptr_t)end)) { ++ memcpy(buf2, er->ext_id, er->len_id); ++ buf2[er->len_id] = '\0'; ++ fprintf(hFile, "* Extension ID: %s\n", buf2); ++ } ++ + +- memcpy(buf, er->ext_id + er->len_id, er->len_des); +- buf[er->len_des] = '\0'; +- fprintf(hFile, "* Extension Descriptor: %s\n", buf); ++ if ((er->len_des < 256) && (er->ext_id + er->len_id + er->len_des < (uintptr_t)end)) { ++ memcpy(buf2, er->ext_id + er->len_id, er->len_des); ++ buf2[er->len_des] = '\0'; ++ fprintf(hFile, "* Extension Descriptor: %s\n", buf2); ++ } + +- memcpy(buf, er->ext_id + er->len_id + er->len_des, +- er->len_src); +- buf[er->len_src] = '\0'; +- fprintf(hFile, "* Extension Spec Source: %s\n", buf); ++ if ((er->len_src < 256) && (er->ext_id + er->len_id + er->len_des + er->len_src < (uintptr_t)end)) { ++ memcpy(buf2, er->ext_id + er->len_id + er->len_des, ++ er->len_src); ++ buf2[er->len_src] = '\0'; ++ fprintf(hFile, "* Extension Spec Source: %s\n", buf2); ++ } + } + buf += head->len; + } +-- +2.43.0 + diff --git a/SPECS/sleuthkit/sleuthkit.spec b/SPECS/sleuthkit/sleuthkit.spec index 1518f3cef9a..f68b5bf2275 100644 --- a/SPECS/sleuthkit/sleuthkit.spec +++ b/SPECS/sleuthkit/sleuthkit.spec @@ -1,12 +1,15 @@ Summary: The Sleuth Kit (TSK) Name: sleuthkit Version: 4.9.0 -Release: 4%{?dist} +Release: 5%{?dist} License: BSD AND CPL AND GPLv2+ AND IBM AND MIT Vendor: Microsoft Corporation Distribution: Mariner URL: https://www.sleuthkit.org Source0: https://github.com/sleuthkit/sleuthkit/releases/download/sleuthkit-%{version}/sleuthkit-%{version}.tar.gz +Patch0: CVE-2026-40024.patch +Patch1: CVE-2026-40025.patch +Patch2: CVE-2026-40026.patch BuildRequires: gcc-c++ BuildRequires: libewf-devel @@ -39,7 +42,7 @@ The %{name}-devel package contains libraries and header files for developing applications that use %{name}. %prep -%setup -q +%autosetup -p1 %build %configure --disable-static \ @@ -140,6 +143,9 @@ find %{buildroot} -type f -name "*.la" -delete -print %{_libdir}/*.so %changelog +* Sat Apr 11 2026 Azure Linux Security Servicing Account - 4.9.0-5 +- Patch for CVE-2026-40026, CVE-2026-40025, CVE-2026-40024 + * Fri Apr 01 2022 Pawel Winogrodzki - 4.9.0-4 - Cleaning-up spec. License verified.