Skip to content

Commit ccc2f51

Browse files
[AutoPR- Security] Patch sleuthkit for CVE-2026-40026, CVE-2026-40025, CVE-2026-40024 [HIGH] (#16540)
Co-authored-by: BinduSri-6522866 <v-badabala@microsoft.com>
1 parent 18417e1 commit ccc2f51

4 files changed

Lines changed: 474 additions & 1 deletion

File tree

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
From 4fb73f3742ead358d4f5cf89ee31032e8d8026e7 Mon Sep 17 00:00:00 2001
2+
From: Brian Carrier <carrier@sleuthkit.org>
3+
Date: Sat, 28 Feb 2026 16:36:48 -0500
4+
Subject: [PATCH] Clean up any path traversal symbols in export path. Reported
5+
by Mobasi
6+
7+
Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com>
8+
Upstream-reference: https://github.com/sleuthkit/sleuthkit/commit/a3f96b3bc36a8bb1a00c297f77110d4a6e7dd31b.patch
9+
---
10+
tools/autotools/tsk_recover.cpp | 49 +++++++++++++++++++++++++++++----
11+
1 file changed, 44 insertions(+), 5 deletions(-)
12+
13+
diff --git a/tools/autotools/tsk_recover.cpp b/tools/autotools/tsk_recover.cpp
14+
index c56edc8..336aa3d 100755
15+
--- a/tools/autotools/tsk_recover.cpp
16+
+++ b/tools/autotools/tsk_recover.cpp
17+
@@ -46,6 +46,17 @@ usage()
18+
exit(1);
19+
}
20+
21+
+
22+
+// special characters we do not want to have in the name when writing out.
23+
+#ifdef TSK_WIN32
24+
+#define TSK_IS_SPL_FILE_CHAR(x) \
25+
+ (((x) == 0x3A) || ((x) == 0x5C))
26+
+#else
27+
+#define TSK_IS_SPL_FILE_CHAR(x) \
28+
+ ((x) == 0x2F)
29+
+#endif
30+
+
31+
+
32+
#ifdef TSK_WIN32
33+
#include <windows.h>
34+
#include "shlobj.h"
35+
@@ -136,8 +147,20 @@ uint8_t TskRecover::writeFile(TSK_FS_FILE * a_fs_file, const char *a_path)
36+
37+
// clean up any control characters
38+
for (size_t i = 0; i < ilen; i++) {
39+
- if (TSK_IS_CNTRL(path8[i]))
40+
+ if (TSK_IS_CNTRL(path8[i])) {
41+
path8[i] = '^';
42+
+ }
43+
+
44+
+ if (path8[i] == '/')
45+
+ path8[i] = '\\';
46+
+
47+
+ // make sure there is no \..\ path traversal
48+
+ if (i + 4 < ilen) {
49+
+ if ((path8[i] == '\\') && (path8[i+1] == '.') && (path8[i+2] == '.') && (path8[i+3] == '\\')) {
50+
+ path8[i+1] = '^';
51+
+ path8[i+2] = '^';
52+
+ }
53+
+ }
54+
}
55+
56+
//convert path from utf8 to utf16
57+
@@ -169,6 +192,8 @@ uint8_t TskRecover::writeFile(TSK_FS_FILE * a_fs_file, const char *a_path)
58+
for (size_t i = 0; i < len; i++) {
59+
if (path16full[i] == L'/')
60+
path16full[i] = L'\\';
61+
+
62+
+ // break at path seperator to make that directory
63+
if (((i > 0) && (path16full[i] == L'\\') && (path16full[i - 1] != L'\\'))
64+
|| ((path16full[i] != L'\\') && (i == len - 1))) {
65+
uint8_t
66+
@@ -198,8 +223,10 @@ uint8_t TskRecover::writeFile(TSK_FS_FILE * a_fs_file, const char *a_path)
67+
char name8[FILENAME_MAX];
68+
strncpy(name8, a_fs_file->name->name, FILENAME_MAX);
69+
for (int i = 0; name8[i] != '\0'; i++) {
70+
- if (TSK_IS_CNTRL(name8[i]))
71+
+ //make sure there is no slash, which could lead to path traversal
72+
+ if (TSK_IS_CNTRL(name8[i]) || TSK_IS_SPL_FILE_CHAR(name8[i])) {
73+
name8[i] = '^';
74+
+ }
75+
}
76+
77+
//convert file name from utf8 to utf16
78+
@@ -257,13 +284,23 @@ uint8_t TskRecover::writeFile(TSK_FS_FILE * a_fs_file, const char *a_path)
79+
for (size_t i = 0; i < strlen(fbuf); i++) {
80+
if (TSK_IS_CNTRL(fbuf[i]))
81+
fbuf[i] = '^';
82+
+
83+
+ // make sure there is no /../ path traversal
84+
+ if (i + 4 < strlen(fbuf)) {
85+
+ if ((fbuf[i] == '/') && (fbuf[i+1] == '.') && (fbuf[i+2] == '.') && (fbuf[i+3] == '/')) {
86+
+ fbuf[i+1] = '^';
87+
+ fbuf[i+2] = '^';
88+
+ }
89+
+ }
90+
}
91+
92+
// see if the directory already exists. Create, if not.
93+
+ // and all of the missing layers
94+
if (0 != lstat(fbuf, &statds)) {
95+
size_t
96+
len = strlen(fbuf);
97+
for (size_t i = 0; i < len; i++) {
98+
+ // stop if we are at a path separator to make that folder
99+
if (((i > 0) && (fbuf[i] == '/') && (fbuf[i - 1] != '/'))
100+
|| ((fbuf[i] != '/') && (i == len - 1))) {
101+
uint8_t
102+
@@ -290,15 +327,17 @@ uint8_t TskRecover::writeFile(TSK_FS_FILE * a_fs_file, const char *a_path)
103+
if (fbuf[strlen(fbuf) - 1] != '/')
104+
strncat(fbuf, "/", PATH_MAX - strlen(fbuf)-1);
105+
106+
+ int nstart = strlen(fbuf);
107+
strncat(fbuf, a_fs_file->name->name, PATH_MAX - strlen(fbuf)-1);
108+
109+
//do name mangling of the file name that was just added
110+
- for (int i = strlen(fbuf)-1; fbuf[i] != '/'; i--) {
111+
- if (TSK_IS_CNTRL(fbuf[i]))
112+
+ for (int i = nstart; fbuf[i] != '\0'; i++) {
113+
+ // need to make sure it doesn't have any slashes in it, which could lead
114+
+ // to path traversal
115+
+ if (TSK_IS_CNTRL(fbuf[i]) || TSK_IS_SPL_FILE_CHAR(fbuf[i]))
116+
fbuf[i] = '^';
117+
}
118+
119+
-
120+
// open the file
121+
if ((hFile = fopen(fbuf, "w+")) == NULL) {
122+
fprintf(stderr, "Error opening file for writing (%s)\n", fbuf);
123+
--
124+
2.45.4
125+
Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
From 0ec7b97fa5ec1fa9af4838c737ae72c71e9c434f Mon Sep 17 00:00:00 2001
2+
From: Brian Carrier <carrier@sleuthkit.org>
3+
Date: Sat, 28 Feb 2026 16:40:19 -0500
4+
Subject: [PATCH] Fix bounds overrun in APFS. Reported by Mobasi
5+
6+
Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com>
7+
Upstream-reference: https://github.com/sleuthkit/sleuthkit/commit/8b9c9e7d493bd68624f3b1a3963edd45c3ff7611.patch
8+
---
9+
tsk/fs/apfs.cpp | 73 ++++++++++++++++++++++++++-------------------
10+
tsk/fs/tsk_apfs.hpp | 22 ++++++++++++--
11+
2 files changed, 62 insertions(+), 33 deletions(-)
12+
13+
diff --git a/tsk/fs/apfs.cpp b/tsk/fs/apfs.cpp
14+
index 229a044..7d38010 100644
15+
--- a/tsk/fs/apfs.cpp
16+
+++ b/tsk/fs/apfs.cpp
17+
@@ -51,22 +51,24 @@ static __forceinline int lsbset(long x) {
18+
#endif // _MSC_VER
19+
20+
class wrapped_key_parser {
21+
- // TODO(JTS): This code assume a well-formed input. It needs some sanity
22+
- // checking!
23+
-
24+
using tag = uint8_t;
25+
using view = span<const uint8_t>;
26+
27+
const uint8_t* _data;
28+
+ const uint8_t* _end; // one-past-the-end of the buffer
29+
30+
- size_t get_length(const uint8_t** pos) const noexcept {
31+
+ // Returns true and leaves *pos unchanged on any bounds violation.
32+
+ bool is_eob(const uint8_t** pos, size_t* out_len) const noexcept {
33+
auto data = *pos;
34+
35+
+ if (data >= _end) return true;
36+
size_t len = *data++;
37+
38+
if (len & 0x80) {
39+
+ size_t enc_len = len & 0x7F;
40+
len = 0;
41+
- auto enc_len = len & 0x7F;
42+
+ if (enc_len == 0 || static_cast<size_t>(_end - data) < enc_len)
43+
+ return true;
44+
while (enc_len--) {
45+
len <<= 8;
46+
len |= *data++;
47+
@@ -74,15 +76,23 @@ class wrapped_key_parser {
48+
}
49+
50+
*pos = data;
51+
- return len;
52+
+ *out_len = len;
53+
+ return false;
54+
}
55+
56+
+ // Returns an invalid (empty) view if the tag is not found or a bounds
57+
+ // violation is detected.
58+
const view get_tag(tag t) const noexcept {
59+
auto data = _data;
60+
61+
- while (true) {
62+
+ while (data < _end) {
63+
const auto tag = *data++;
64+
- const auto len = get_length(&data);
65+
+
66+
+ size_t len = 0;
67+
+ if (is_eob(&data, &len)) break;
68+
+
69+
+ // Ensure the value bytes are within the buffer.
70+
+ if (static_cast<size_t>(_end - data) < len) break;
71+
72+
if (tag == t) {
73+
return {data, len};
74+
@@ -90,6 +100,9 @@ class wrapped_key_parser {
75+
76+
data += len;
77+
}
78+
+
79+
+ // Tag not found or buffer overrun — return an invalid view.
80+
+ return {};
81+
}
82+
83+
// Needed for the recursive variadic to compile, but should never be
84+
@@ -99,7 +112,9 @@ class wrapped_key_parser {
85+
}
86+
87+
public:
88+
- wrapped_key_parser(const void* data) noexcept : _data{(const uint8_t*)data} {}
89+
+ wrapped_key_parser(const void* data, size_t size) noexcept
90+
+ : _data{static_cast<const uint8_t*>(data)},
91+
+ _end{static_cast<const uint8_t*>(data) + size} {}
92+
93+
template <typename... Args>
94+
const view get_data(tag t, Args... args) const noexcept {
95+
@@ -109,7 +124,8 @@ class wrapped_key_parser {
96+
return data;
97+
}
98+
99+
- return wrapped_key_parser{data.data()}.get_data(args...);
100+
+ // Recurse into the nested TLV value; its buffer is exactly `data`.
101+
+ return wrapped_key_parser{data.data(), data.count()}.get_data(args...);
102+
}
103+
104+
template <typename... Args>
105+
@@ -346,10 +362,10 @@ APFSFileSystem::APFSFileSystem(const APFSPool& pool,
106+
}
107+
108+
APFSFileSystem::wrapped_kek::wrapped_kek(TSKGuid&& id,
109+
- const std::unique_ptr<uint8_t[]>& kp)
110+
+ const APFS_sized_key_data& kp)
111+
: uuid{std::forward<TSKGuid>(id)} {
112+
// Parse KEK
113+
- wrapped_key_parser wp{kp.get()};
114+
+ wrapped_key_parser wp{kp.get(), kp.size};
115+
116+
// Get flags
117+
flags = wp.get_number(0x30, 0xA3, 0x82);
118+
@@ -398,12 +414,12 @@ void APFSFileSystem::init_crypto_info() {
119+
const auto container_kb = _pool.nx()->keybag();
120+
121+
auto data = container_kb.get_key(uuid(), APFS_KB_TYPE_VOLUME_KEY);
122+
- if (data == nullptr) {
123+
+ if (!data) {
124+
throw std::runtime_error(
125+
"APFSFileSystem: can not find volume encryption key");
126+
}
127+
128+
- wrapped_key_parser wp{ data.get() };
129+
+ wrapped_key_parser wp{ data.get(), data.size };
130+
131+
// Get Wrapped VEK
132+
auto kek_data = wp.get_data(0x30, 0xA3, 0x83);
133+
@@ -424,7 +440,7 @@ void APFSFileSystem::init_crypto_info() {
134+
std::memcpy(_crypto.vek_uuid, kek_data.data(), sizeof(_crypto.vek_uuid));
135+
136+
data = container_kb.get_key(uuid(), APFS_KB_TYPE_UNLOCK_RECORDS);
137+
- if (data == nullptr) {
138+
+ if (!data) {
139+
throw std::runtime_error(
140+
"APFSFileSystem: can not find volume recovery key");
141+
}
142+
@@ -443,7 +459,7 @@ void APFSFileSystem::init_crypto_info() {
143+
144+
data = recs.get_key(uuid(), APFS_KB_TYPE_PASSPHRASE_HINT);
145+
146+
- if (data != nullptr) {
147+
+ if (data) {
148+
_crypto.password_hint = std::string((const char*)data.get());
149+
}
150+
151+
@@ -1000,10 +1016,10 @@ APFSKeybag::APFSKeybag(const APFSPool& pool, const apfs_block_num block_num,
152+
}
153+
}
154+
155+
-std::unique_ptr<uint8_t[]> APFSKeybag::get_key(const TSKGuid& uuid,
156+
- uint16_t type) const {
157+
+APFS_sized_key_data APFSKeybag::get_key(const TSKGuid& uuid,
158+
+ uint16_t type) const {
159+
if (kb()->num_entries == 0) {
160+
- return nullptr;
161+
+ return {};
162+
}
163+
164+
// First key is immediately after the header
165+
@@ -1012,20 +1028,17 @@ std::unique_ptr<uint8_t[]> APFSKeybag::get_key(const TSKGuid& uuid,
166+
for (auto i = 0U; i < kb()->num_entries; i++) {
167+
if (next_key->type == type &&
168+
std::memcmp(next_key->uuid, uuid.bytes().data(), 16) == 0) {
169+
- // We've found a matching key. Copy it's data to a pointer and return it.
170+
+ // We've found a matching key. Copy its data to a pointer and return it.
171+
const auto data = reinterpret_cast<const uint8_t*>(next_key + 1);
172+
173+
- // We're padding the data with an extra byte so we can null-terminate
174+
- // any data strings. There might be a better way.
175+
+ // +1 byte for null-terminator guard on string values
176+
auto dp = std::make_unique<uint8_t[]>(next_key->length + 1);
177+
-
178+
std::memcpy(dp.get(), data, next_key->length);
179+
180+
- return dp;
181+
+ return {std::move(dp), next_key->length};
182+
}
183+
184+
// Calculate address of next key (ensuring alignment)
185+
-
186+
const auto nk_addr =
187+
(uintptr_t)next_key +
188+
((sizeof(*next_key) + next_key->length + 0x0F) & ~0x0FULL);
189+
@@ -1034,7 +1047,7 @@ std::unique_ptr<uint8_t[]> APFSKeybag::get_key(const TSKGuid& uuid,
190+
}
191+
192+
// Not Found
193+
- return nullptr;
194+
+ return {};
195+
}
196+
197+
std::vector<APFSKeybag::key> APFSKeybag::get_keys() const {
198+
@@ -1046,13 +1059,13 @@ std::vector<APFSKeybag::key> APFSKeybag::get_keys() const {
199+
for (auto i = 0U; i < kb()->num_entries; i++) {
200+
const auto data = reinterpret_cast<const uint8_t*>(next_key + 1);
201+
202+
- // We're padding the data with an extra byte so we can null-terminate
203+
- // any data strings. There might be a better way.
204+
+ // +1 byte for null-terminator guard on string values
205+
auto dp = std::make_unique<uint8_t[]>(next_key->length + 1);
206+
-
207+
std::memcpy(dp.get(), data, next_key->length);
208+
209+
- keys.emplace_back(key{{next_key->uuid}, std::move(dp), next_key->type});
210+
+ keys.emplace_back(key{{next_key->uuid},
211+
+ APFS_sized_key_data{std::move(dp), next_key->length},
212+
+ next_key->type});
213+
214+
// Calculate address of next key (ensuring alignment)
215+
const auto nk_addr =
216+
diff --git a/tsk/fs/tsk_apfs.hpp b/tsk/fs/tsk_apfs.hpp
217+
index 700cd54..33629e0 100755
218+
--- a/tsk/fs/tsk_apfs.hpp
219+
+++ b/tsk/fs/tsk_apfs.hpp
220+
@@ -37,6 +37,22 @@ constexpr T bitfield_value(T bitfield, int bits, int shift) noexcept {
221+
222+
class APFSPool;
223+
224+
+// An owning buffer that also carries its own length, so callers never need to
225+
+// track the size separately. Drop-in replacement for unique_ptr<uint8_t[]>
226+
+// at call sites — supports operator bool() and .get() for compatibility.
227+
+struct APFS_sized_key_data {
228+
+ std::unique_ptr<uint8_t[]> ptr;
229+
+ size_t size{0};
230+
+
231+
+ // Allows `if (data)` / `if (!data)` checks to keep working.
232+
+ explicit operator bool() const noexcept { return ptr != nullptr; }
233+
+
234+
+ // Mimic unique_ptr's .get() so existing call sites need minimal changes.
235+
+ const uint8_t* get() const noexcept { return ptr.get(); }
236+
+};
237+
+
238+
+
239+
+
240+
class APFSObject : public APFSBlock {
241+
protected:
242+
inline const apfs_obj_header *obj() const noexcept {
243+
@@ -856,7 +872,7 @@ class APFSKeybag : public APFSObject {
244+
245+
using key = struct {
246+
TSKGuid uuid;
247+
- std::unique_ptr<uint8_t[]> data;
248+
+ APFS_sized_key_data data;
249+
uint16_t type;
250+
};
251+
252+
@@ -864,7 +880,7 @@ class APFSKeybag : public APFSObject {
253+
APFSKeybag(const APFSPool &pool, const apfs_block_num block_num,
254+
const uint8_t *key, const uint8_t *key2 = nullptr);
255+
256+
- std::unique_ptr<uint8_t[]> get_key(const TSKGuid &uuid, uint16_t type) const;
257+
+ APFS_sized_key_data get_key(const TSKGuid &uuid, uint16_t type) const;
258+
259+
std::vector<key> get_keys() const;
260+
};
261+
@@ -993,7 +1009,7 @@ class APFSFileSystem : public APFSObject {
262+
uint64_t iterations;
263+
uint64_t flags;
264+
uint8_t salt[0x10];
265+
- wrapped_kek(TSKGuid &&uuid, const std::unique_ptr<uint8_t[]> &);
266+
+ wrapped_kek(TSKGuid &&uuid, const APFS_sized_key_data &);
267+
268+
inline bool hw_crypt() const noexcept {
269+
// If this bit is set, some sort of hardware encryption is used.
270+
--
271+
2.45.4
272+

0 commit comments

Comments
 (0)