[PATCH v2 3/4] thunderbolt: property: cap recursion depth in __tb_property_parse_dir()

Michael Bommarito
2026-04-15T12:32:30Z

A DIRECTORY entry's value field is used as the dir_offset for a
recursive call into __tb_property_parse_dir() with no depth counter.
A crafted peer that chains DIRECTORY entries into a back-reference
loop drives the parser until the kernel stack is exhausted and the
guard page fires. Any untrusted XDomain peer (cable, dock, in-line
inspector, adjacent host) that reaches the PROPERTIES_REQUEST
control-plane exchange can trigger this without authentication.

Thread a depth counter through tb_property_parse() and
__tb_property_parse_dir(), and reject blocks that exceed
TB_PROPERTY_MAX_DEPTH = 8. That is comfortably larger than any
observed legitimate XDomain layout.

Operators who do not need XDomain host-to-host discovery can disable
the path entirely with thunderbolt.xdomain=0 on the kernel command
line.

Fixes: e69b6c02b4c3 ("thunderbolt: Add functions for parsing and creating XDomain property blocks")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-4-6
Assisted-by: Codex:gpt-5-4
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
---
drivers/thunderbolt/property.c | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)

diff --git a/drivers/thunderbolt/property.c b/drivers/thunderbolt/property.c
index 274b555d27c8..99ee7089456c 100644
--- a/drivers/thunderbolt/property.c
+++ b/drivers/thunderbolt/property.c
@@ -35,10 +35,11 @@ struct tb_property_dir_entry {
};

#define TB_PROPERTY_ROOTDIR_MAGIC 0x55584401
+#define TB_PROPERTY_MAX_DEPTH 8

static struct tb_property_dir *__tb_property_parse_dir(const u32 *block,
size_t block_len, unsigned int dir_offset, size_t dir_len,
- bool is_root);
+ bool is_root, unsigned int depth);

static inline void parse_dwdata(void *dst, const void *src, size_t dwords)
{
@@ -97,7 +98,8 @@ tb_property_alloc(const char *key, enum tb_property_type type)
}

static struct tb_property *tb_property_parse(const u32 *block, size_t block_len,
- const struct tb_property_entry *entry)
+ const struct tb_property_entry *entry,
+ unsigned int depth)
{
char key[TB_PROPERTY_KEY_SIZE + 1];
struct tb_property *property;
@@ -118,7 +120,7 @@ static struct tb_property *tb_property_parse(const u32 *block, size_t block_len,
switch (property->type) {
case TB_PROPERTY_TYPE_DIRECTORY:
dir = __tb_property_parse_dir(block, block_len, entry->value,
- entry->length, false);
+ entry->length, false, depth + 1);
if (!dir) {
kfree(property);
return NULL;
@@ -163,13 +165,17 @@ static struct tb_property *tb_property_parse(const u32 *block, size_t block_len,
}

static struct tb_property_dir *__tb_property_parse_dir(const u32 *block,
- size_t block_len, unsigned int dir_offset, size_t dir_len, bool is_root)
+ size_t block_len, unsigned int dir_offset, size_t dir_len, bool is_root,
+ unsigned int depth)
{
const struct tb_property_entry *entries;
size_t i, content_len, nentries;
unsigned int content_offset;
struct tb_property_dir *dir;

+ if (depth > TB_PROPERTY_MAX_DEPTH)
+ return NULL;
+
dir = kzalloc_obj(*dir);
if (!dir)
return NULL;
@@ -199,7 +205,8 @@ static struct tb_property_dir *__tb_property_parse_dir(const u32 *block,
for (i = 0; i < nentries; i++) {
struct tb_property *property;

- property = tb_property_parse(block, block_len, &entries[i]);
+ property = tb_property_parse(block, block_len, &entries[i],
+ depth);
if (!property) {
tb_property_free_dir(dir);
return NULL;
@@ -238,7 +245,7 @@ struct tb_property_dir *tb_property_parse_dir(const u32 *block,
return NULL;

return __tb_property_parse_dir(block, block_len, 0, rootdir->length,
- true);
+ true, 0);
}

/**
--
2.53.0