thunderbolt: property: Cap recursion depth in __tb_property_parse_dir()
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: cdae7c07e3 ("thunderbolt: Add support for XDomain properties")
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>
Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
master
parent
de21b59c29
commit
928abe19fb
|
|
@ -35,10 +35,11 @@ struct tb_property_dir_entry {
|
||||||
};
|
};
|
||||||
|
|
||||||
#define TB_PROPERTY_ROOTDIR_MAGIC 0x55584401
|
#define TB_PROPERTY_ROOTDIR_MAGIC 0x55584401
|
||||||
|
#define TB_PROPERTY_MAX_DEPTH 8
|
||||||
|
|
||||||
static struct tb_property_dir *__tb_property_parse_dir(const u32 *block,
|
static struct tb_property_dir *__tb_property_parse_dir(const u32 *block,
|
||||||
size_t block_len, unsigned int dir_offset, size_t dir_len,
|
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)
|
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,
|
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];
|
char key[TB_PROPERTY_KEY_SIZE + 1];
|
||||||
struct tb_property *property;
|
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) {
|
switch (property->type) {
|
||||||
case TB_PROPERTY_TYPE_DIRECTORY:
|
case TB_PROPERTY_TYPE_DIRECTORY:
|
||||||
dir = __tb_property_parse_dir(block, block_len, entry->value,
|
dir = __tb_property_parse_dir(block, block_len, entry->value,
|
||||||
entry->length, false);
|
entry->length, false, depth + 1);
|
||||||
if (!dir) {
|
if (!dir) {
|
||||||
kfree(property);
|
kfree(property);
|
||||||
return NULL;
|
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,
|
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;
|
const struct tb_property_entry *entries;
|
||||||
size_t i, content_len, nentries;
|
size_t i, content_len, nentries;
|
||||||
unsigned int content_offset;
|
unsigned int content_offset;
|
||||||
struct tb_property_dir *dir;
|
struct tb_property_dir *dir;
|
||||||
|
|
||||||
|
if (depth > TB_PROPERTY_MAX_DEPTH)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
dir = kzalloc_obj(*dir);
|
dir = kzalloc_obj(*dir);
|
||||||
if (!dir)
|
if (!dir)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
@ -200,7 +206,7 @@ static struct tb_property_dir *__tb_property_parse_dir(const u32 *block,
|
||||||
for (i = 0; i < nentries; i++) {
|
for (i = 0; i < nentries; i++) {
|
||||||
struct tb_property *property;
|
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) {
|
if (!property) {
|
||||||
tb_property_free_dir(dir);
|
tb_property_free_dir(dir);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
@ -239,7 +245,7 @@ struct tb_property_dir *tb_property_parse_dir(const u32 *block,
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
return __tb_property_parse_dir(block, block_len, 0, rootdir->length,
|
return __tb_property_parse_dir(block, block_len, 0, rootdir->length,
|
||||||
true);
|
true, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue