From nobody Tue Jun 16 01:35:59 2026 Received: from mail-qv1-f51.google.com (mail-qv1-f51.google.com [209.85.219.51]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2B440316189 for ; Wed, 15 Apr 2026 03:23:53 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.51 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776223434; cv=none; b=rEp4x0cZHwud2qxQ37QiHXrHazXpgFV7lLE+uBbImRe+2NDRGcpc9Mxn+pE5/cHP7M4xE4B7fxgZiELALTmFm2AFb09jdlhrF8aw2aW1sVlaoGc+Q4jCiuRUNWxWhpjTD/eXsDWBveMi4t3rvhwqHdyRd+VCgusoMKYqxGwIiKo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776223434; c=relaxed/simple; bh=c7fhQU1kmA/OElXvm8TnzPS64pwv6uUDN6n799rAG68=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=qJMzEkUIFjRmezTa0zKXZk6C6Up/S1LGIoCvRmIc4H9cobeMA/DcjPLOw+Y9LaA7GXbE8Wrc5pVz1Amkclzv6QG63cXfOT5R+7IlqdSEaLr+VE6tH37yUaOLODL0HJvGcwREGmnsSAnVbysMKGQOBlthfFN7izlRdb/j4DXi1ok= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=I8LIGHIv; arc=none smtp.client-ip=209.85.219.51 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="I8LIGHIv" Received: by mail-qv1-f51.google.com with SMTP id 6a1803df08f44-8aca0469204so35021056d6.2 for ; Tue, 14 Apr 2026 20:23:53 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1776223432; x=1776828232; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=Kt6/nBfZ3xp0yA3aOUH7lrksq26RDqGq7BPA0rF82PY=; b=I8LIGHIvX4nPkX9meKsaTB7B81xLzqeQ92i/rtzw4hHJfnZiLd6BPsgGJNPNX5xAz5 OsOqZYfPDp57fRnSoHG90B/ga+w1P6KrdG0elJtbYH9oD+O7Ic5+vCnh3tfsQuqeOGTK 7gmtBuUoDTFy7O57kWeiUl3bCsTXpTP/uMUCZcjMy1uGVl/3dfGRT1rMuJw4yzauEpSr xnOb+TqDUaP+IDlBsSFzlOT31PvIe/InADfZio2jrFXcqJ7uA8jjzrP94LM7koza/xkY zqzSVO0JLs8W+Uea7hWJSmOmX4oiwMCYKnj/oBb+g7t1fdnMWEsZxr9OFUDet/UBbJPE +H6A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1776223432; x=1776828232; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=Kt6/nBfZ3xp0yA3aOUH7lrksq26RDqGq7BPA0rF82PY=; b=UyaRdHEM98avid8q/o1Xw3xz2YZdFrzYGPQXI4wLQeklxxl1qqWonZIL/gDh/iITtg dVVfvQ/hp6lwUnYkDmWY+XK6xF+4QuF7q+NYPyRf8IGsNxJXp5EmRNDO0GMH/0NXxtQY 3KGPNs5LY79vghGBa5fVsNqajEzJ0QVj+YjdL5eXHV3NgbrE5b+Li6W9RO+IzrmYNWAA chzz1pBT7kLHfSQMiof44ypZ9eTIh/XzERYhfPsvabxD7J8vbJ6slDQMaYpcbI5y7MBX PlsI1be4x/DmLYzP31SNG97ORFTl4n1jWNdIJrKVn1tXxRI89c4xQhzJm6a71h+yeqhd xctA== X-Forwarded-Encrypted: i=1; AFNElJ+V4XRQ0n8FM+JxCvWhZEAwrind5h9/2luh+om3vsrZrLttxnoL0lYuSsEIDfufyi6tgBaesmUJ/oq+uO8=@vger.kernel.org X-Gm-Message-State: AOJu0Yy/4LTg2MoP3uRSlsLtLfmYrW0JKbZru0hJ3O//6zKCqx2SK2Bz jJJntZGbN5gZupCbh8tCP+BHpb8ACSVc9QdMCpPTV+KdacTKiSjP6+LG X-Gm-Gg: AeBDievFSZyQGOiuNukQOQg9iU3EGv6UOYVY9eMc75MzMULlDXLWj+ukeUNs3tRPtNh GBXKIMt7kJaSuYsgh+IzHhHtaG6LDKIBHKhj7uDDpF5jKFU428GQsDCeeyZDzWbGPJI/OGdVIaM EGXp545oAqdT3lZmPanWeF/MoF1ZsidKk6F6iI9Szbo/GAiwHXOwtZBs64HLpbAsCdGtZlk4VsI 2z2Fp+4pTytUZRGVzBBOST87fdW56MkBRQ3xyKPGfsUFDFiPWls4QCsKYT7+15bIyTtxt5mZcrx KoeFdycQUSI0O8SFLm/5waiCmGwngU46a4ah68gv6fuND9CHjEbqpW+rTm3e6cTGgLzBc6BhxCG E5SZWVCGdE70Qv+NpU79TUxlEFsFx04lFPKLmgWbNMk7hvYdMhBoQyWFWMCAMtZqHCIyNu2xw1W BhICPKal7FG4IYNpcPmuPPcvtQJwZ9fPFLJPNtb+GfEgy3o8UElttWlCWTeAb2LXRq7B8kxPTNv o4wSVR73m4EkwMKmBESNio5BFx490I0/SZvzyAG2w== X-Received: by 2002:a05:622a:1822:b0:50d:736a:6248 with SMTP id d75a77b69052e-50dd5b30c24mr318069561cf.11.1776223432105; Tue, 14 Apr 2026 20:23:52 -0700 (PDT) Received: from server0 (c-68-48-65-54.hsd1.mi.comcast.net. [68.48.65.54]) by smtp.gmail.com with ESMTPSA id d75a77b69052e-50e1b012b23sm3713071cf.30.2026.04.14.20.23.50 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 14 Apr 2026 20:23:51 -0700 (PDT) From: Michael Bommarito To: linux-usb@vger.kernel.org, Mika Westerberg Cc: Andreas Noever , Yehezkel Bernat , Greg Kroah-Hartman , linux-kernel@vger.kernel.org, stable@vger.kernel.org Subject: [PATCH 1/2] thunderbolt: property: harden XDomain property parser against crafted peer Date: Tue, 14 Apr 2026 23:23:34 -0400 Message-ID: <20260415032335.2826412-2-michael.bommarito@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260415032335.2826412-1-michael.bommarito@gmail.com> References: <20260415032335.2826412-1-michael.bommarito@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Three independent memory-safety defects in drivers/thunderbolt/property.c are reachable when an untrusted Thunderbolt/USB4 XDomain peer responds to a PROPERTIES_REQUEST during host-to-host discovery. The peer supplies up to TB_XDP_PROPERTIES_MAX_LENGTH (500) dwords of attacker-controlled property block which the local host passes to tb_property_parse_dir() before PCIe tunnel authorization. Bug A - u32 overflow in tb_property_entry_valid() (property.c:61). if (entry->value + entry->length > block_len) return false; entry->value is u32, entry->length is u16. The sum is computed in u32 and wraps. With block_len =3D 500 an attacker picks value =3D 0xFFFFFF00, length =3D 0x100; the u32 sum 0x100000000 wraps to 0, passing the > block_len check. tb_property_parse() then runs parse_dwdata(property->value.data, block + entry->value, entry->length); where block is const u32 * and entry->value is promoted to size_t for the pointer arithmetic. The read source is block + (u64)value * 4, tens of GiB past the property-block allocation. Up to entry->length * 4 bytes are read from there into a freshly kcalloc'd property->value.data or property->value.text buffer. Exfiltration path for TB_PROPERTY_TYPE_TEXT on the "deviceid" or "vendorid" keys: populate_properties() (xdomain.c:1157,1162) runs kstrdup(p->value.text, ...) into xd->device_name / xd->vendor_name, which are read back via the per-XDomain device_name and vendor_name sysfs attributes (xdomain.c:1730, 1763). kstrdup stops at the first NUL byte in the OOB region, so the usable leak is the prefix up to the first zero byte at an attacker-chosen offset past the property block. The attacker does not know block's KASLR/slab placement, so the read is untargeted in absolute terms - they pick a delta, not an address. There is no generic "properties" sysfs blob; DATA-typed properties are parsed into property->value.data but never generically surfaced to userspace, so only the TEXT path with the two named keys is exfil-reachable. NUL-bounded, untargeted, but still an attacker-directed OOB read. Replace the u32 addition with check_add_overflow() so a wrapped sum is rejected. Bug B - unbounded recursion in __tb_property_parse_dir(). A DIRECTORY entry's value field is used as dir_offset for a recursive call with no depth counter. A peer that crafts a back- reference chain drives the parser until the 16 KiB kernel stack is exhausted and the guard page fires - pre-authentication remote DoS. Bound the recursion to TB_PROPERTY_MAX_DEPTH =3D 8, comfortably larger than any legitimate XDomain property layout. Bug C - size_t underflow on dir_len - 4 (property.c:184). content_offset =3D dir_offset + 4; content_len =3D dir_len - 4; /* Length includes UUID */ dir_len arrives as a size_t sourced from entry->length (u16) on the non-root path. If entry->length < 4, the subtraction underflows size_t to ~SIZE_MAX, nentries becomes SIZE_MAX / 4, and the loop walks entries past the property block on each iteration, reading OOB until either an entry fails validation or the kernel oopses on an unmapped page. Reject dir_len < 4 explicitly on the non-root path. Additional hardening: move INIT_LIST_HEAD(&dir->properties) to immediately after dir allocation so every error-return path that calls tb_property_free_dir() (including the new dir_len path and the pre-existing dir->uuid alloc-failure path at property.c:180) sees a walkable empty list rather than the zero-initialized NULL next/prev that would oops list_for_each_entry_safe(). All three defects are OOB-read plus DoS class. No controlled OOB write is reachable through the parser; parse_dwdata's destination is a freshly kcalloc'd buffer sized by entry->length. Attacker model: malicious Thunderbolt/USB4 XDomain peer (cable, dock, in-line inspector, adjacent host). Discovery runs as soon as the link is trained; PCIe tunnel authorization does not gate the control-plane PROPERTIES_REQUEST/RESPONSE path, and the host IOMMU does not mitigate because the data arrives as a control- plane payload the driver willingly copies into its own buffer before parsing. Reproduced on v7.0-rc7 + CONFIG_KASAN=3Dy + CONFIG_USB4_KUNIT_TEST=3Dy via the companion KUnit suite in the sibling patch. Pre-fix, each of the three cases oopses inside __tb_property_parse_dir (Bug A hits a KASAN shadow-memory fault, Bug B trips the stack guard, Bug C OOB-reads past the property block). Post-fix, all three tests return NULL cleanly and pass. The parser sites fixed here have not been touched since the initial 2017 XDomain landing, per git log -p. property.c has had three prior fixes by Kangjie Lu in 2019 (106204b56f60, e4dfdd5804cc, 6183d5a51866) for NULL-check omissions on kzalloc/kmemdup returns, and a 2025 documentation cleanup by Alan Borzeszkowski (d015642ad36d); none of those touch the bounds / arithmetic / recursion sites this patch addresses. Verified via lei queries against lore.kernel.org/linux-usb/ for dfn:drivers/thunderbolt/property.c, dfhh:tb_property_parse_dir, dfhh:tb_property_entry_valid (0 hits beyond the doc cleanup); Patchwork linux-usb "thunderbolt property" query (0 in-flight patches); and Mika Westerberg's westeri/thunderbolt.git next/fixes/master branches (no pending bounds work on this file). Fixes: e69b6c02b4c3 ("thunderbolt: Add functions for parsing and creating X= Domain 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 --- drivers/thunderbolt/property.c | 67 +++++++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 9 deletions(-) diff --git a/drivers/thunderbolt/property.c b/drivers/thunderbolt/property.c index 50cbfc92fe65..57ec742ed210 100644 --- a/drivers/thunderbolt/property.c +++ b/drivers/thunderbolt/property.c @@ -8,11 +8,21 @@ */ =20 #include +#include #include #include #include #include =20 +/* + * Bounds recursion depth when parsing a malicious XDomain property + * block whose DIRECTORY entries are crafted to self-refer. The + * XDomain spec gives no hard limit; 8 is comfortably larger than any + * legitimate property layout observed in practice and leaves the + * kernel stack headroom. + */ +#define TB_PROPERTY_MAX_DEPTH 8 + struct tb_property_entry { u32 key_hi; u32 key_lo; @@ -37,7 +47,7 @@ struct tb_property_dir_entry { =20 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); =20 static inline void parse_dwdata(void *dst, const void *src, size_t dwords) { @@ -52,13 +62,23 @@ static inline void format_dwdata(void *dst, const void = *src, size_t dwords) static bool tb_property_entry_valid(const struct tb_property_entry *entry, size_t block_len) { + u32 end; + switch (entry->type) { case TB_PROPERTY_TYPE_DIRECTORY: case TB_PROPERTY_TYPE_DATA: case TB_PROPERTY_TYPE_TEXT: if (entry->length > block_len) return false; - if (entry->value + entry->length > block_len) + /* + * entry->value is u32 and entry->length is u16; the sum is + * performed in u32 and wraps for crafted inputs. Use an + * overflow-aware check so a wrapped sum is rejected instead + * of appearing to satisfy the bound. + */ + if (check_add_overflow(entry->value, (u32)entry->length, &end)) + return false; + if (end > block_len) return false; break; =20 @@ -93,7 +113,8 @@ tb_property_alloc(const char *key, enum tb_property_type= type) } =20 static struct tb_property *tb_property_parse(const u32 *block, size_t bloc= k_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; @@ -114,7 +135,8 @@ static struct tb_property *tb_property_parse(const u32 = *block, size_t block_len, switch (property->type) { case TB_PROPERTY_TYPE_DIRECTORY: dir =3D __tb_property_parse_dir(block, block_len, entry->value, - entry->length, false); + entry->length, false, + depth + 1); if (!dir) { kfree(property); return NULL; @@ -159,16 +181,33 @@ static struct tb_property *tb_property_parse(const u3= 2 *block, size_t block_len, } =20 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; =20 + /* + * A malicious XDomain peer can craft DIRECTORY entries whose + * offsets point back at their own container, making the recursion + * unbounded without this gate. + */ + if (depth > TB_PROPERTY_MAX_DEPTH) + return NULL; + dir =3D kzalloc_obj(*dir); if (!dir) return NULL; + /* + * Initialize the list head immediately so every error-return path + * that calls tb_property_free_dir() (the new dir_len reject and + * the existing uuid-alloc failure path) sees a walkable empty + * list rather than the zero-initialized NULL next/prev that + * would oops list_for_each_entry_safe(). + */ + INIT_LIST_HEAD(&dir->properties); =20 if (is_root) { content_offset =3D dir_offset + 2; @@ -181,18 +220,28 @@ static struct tb_property_dir *__tb_property_parse_di= r(const u32 *block, return NULL; } content_offset =3D dir_offset + 4; + /* + * dir_len arrives here as the u16 entry->length widened to + * size_t; values below 4 underflow size_t on the subtraction + * below and produce a gigantic content_len, driving the + * nentries loop off the block with OOB reads on each + * iteration. + */ + if (dir_len < 4) { + tb_property_free_dir(dir); + return NULL; + } content_len =3D dir_len - 4; /* Length includes UUID */ } =20 entries =3D (const struct tb_property_entry *)&block[content_offset]; nentries =3D content_len / (sizeof(*entries) / 4); =20 - INIT_LIST_HEAD(&dir->properties); - for (i =3D 0; i < nentries; i++) { struct tb_property *property; =20 - property =3D tb_property_parse(block, block_len, &entries[i]); + property =3D tb_property_parse(block, block_len, &entries[i], + depth); if (!property) { tb_property_free_dir(dir); return NULL; @@ -231,7 +280,7 @@ struct tb_property_dir *tb_property_parse_dir(const u32= *block, return NULL; =20 return __tb_property_parse_dir(block, block_len, 0, rootdir->length, - true); + true, 0); } =20 /** --=20 2.53.0 From nobody Tue Jun 16 01:35:59 2026 Received: from mail-qt1-f182.google.com (mail-qt1-f182.google.com [209.85.160.182]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6B2E9319859 for ; Wed, 15 Apr 2026 03:23:54 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.160.182 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776223435; cv=none; b=WIczGja1OkkYvBUD0XXX2TulewWoClHaVTQ5RuFexyCRlNcKi4EXHyiLK8A8iLD6nBCBqCUAXk7VQ3DT3XqawKcOVi2V8AwiAgV2EGqgaH1btGFVeFJfAiTF5fV8O5pMXoBWwxo+nJhkzVSakRWBwEFwVMNJp6HLZaBgKzNakTU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776223435; c=relaxed/simple; bh=rQDYqSU37WRbCB4Qm55uxEWs586IyL2RigYzcvg0OTc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Pyz//W/aPP4j4EcMY/rsO8THb+kA5C0rXY08wPlPHNpTCa5kvnl3OYwTS/TL+kWTLL6Si0zKCJ4eOnK+SlC0r09LmmCOcQmJFYU0eYlugHHRc80ifpdH1fyKgsysKJL+/CFu2ZAe1z8XZj+9xb+uskJB/OXOKHgEMSx1el6xTkc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=DLPBVlDG; arc=none smtp.client-ip=209.85.160.182 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="DLPBVlDG" Received: by mail-qt1-f182.google.com with SMTP id d75a77b69052e-50d58c513dbso46016291cf.2 for ; Tue, 14 Apr 2026 20:23:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1776223433; x=1776828233; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=nH3KHfCMOZkc58NmOsw59J1qT/TVXoniVUUkFi7xDGY=; b=DLPBVlDG1TPk1DPbAteXLMAk7OCvTKrRVbdERoqRifebz3elsgseV60A5phfwlbLWr 9l8xAcMPpmUv1jVeeRVXEltUpyQL0WTPDAyaE5umDrcUKrB4WSVxfT9UmibBP3ZkQRFK P2V/dx0c07CY0cX/nFelr/3EOvNoFUoCOQPZCdgSBl2MqDFE0irMpYl6DEVc3jGrSY7C MBDu3/nn9Dg13N/hTXKyH42nt9PANiBpOQBepSWsWPNiupmY8A9idC1epaEQlJV0WZB6 x+QwiYncfCWQXUwx7AaUtmgO5P+zDwDPJ1Am5GMK+scTS89cuLs5/g0D4kSulMtJ5sb0 IAPQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1776223433; x=1776828233; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=nH3KHfCMOZkc58NmOsw59J1qT/TVXoniVUUkFi7xDGY=; b=BnxECnwlWrKQMUVk330KBMLVpva4eE9y0oLMCw6jblEcRO1YaFRCt0elvidJVCT1L9 ZD8uvELMEwDNgAV8TMhGm5v2dwq4biGfegGTrO7AXymk+J8dcTkPWLLOPLdrRkxSK4Ou birldeMQnN4fDzyliztCPW/s8PUOw9tMjQbKihoit/83eLn0SQosFCBCDi3ObDyj6o2m AsT5jh33fb/FHAW6D9o+2BMQVNIKsaFqnvJSnQyVMFYwkeuvgaugog9zzevvmqiAr5pL n3PXLER/0V8+N2dNpI0zOwqturHiJ4bQgCyIicnf2UBVHrurfyD1dg3z1ENfLcZ8VMR2 +cnQ== X-Forwarded-Encrypted: i=1; AFNElJ8VWsceY0GvuUtZTXctKyqmDIrFWxSddKz/H8dOxXQNE2T6XDpSI/jymkL/jpWX2QwH0Hq2oEsjFVQ+tVo=@vger.kernel.org X-Gm-Message-State: AOJu0Yz9y7ntPh8Duia8l+r9B/BaP/4lYR5FcFlQ6i5m1NCoQ2Sffb3S K1H1C9ceNmSAH8K5N8Vq/9lkvKUrxfY64+XEPqfm+dwg1ppttcNvm0cV X-Gm-Gg: AeBDievl0zWmNsYtBcSIF3k73JOLJysMNzW8439z6QlHcdgwhIzrJBQ7zpRzIq9Ykxo BDAzv5y4X76qaO7mDIYXI4Vq0tlPUgUg9FJSQNS+az5aP9tQI7/nTNbb7J0mLqYFED3cglz1NRe zUtvYndm32lvNR2a6EDzaZGGxZY88XreEZPmFJwhhbB/bNA+RKooB/CDjL47qlmHyB7zMwSnuWU LUzcjow0WzDh4/6vjAZSvyw0wJ+lRxCfjPMGphBUCoQlineOnS2s1IjjnJZ1d3+3X7z9K4pi6IB xbAicIazKpLrCySancgr710Qz2s/hOdyMaXrTq6aQV2/x/FbUtCiq0GORtmyGFfXpUcbZiyceG5 1IDX+PPf07/s7iHGtGSHxG+Q2k3qyVLGJ+gbyt5YrS0I8c3KW4KPuU3u/GL2YUKby2CA/ZBkNuC ZQRi5X6yj8yR88oO94qYy3qAdcJ8YdWuDaeiLBKgs4lKFzpO3G0JrN1VoJR/GYp/aLirvt7XNrw Mka1+Buvofm8bXcOtFWylB6d3HmXw4= X-Received: by 2002:ac8:7c41:0:b0:50d:a747:9e95 with SMTP id d75a77b69052e-50dd5aef3famr288781941cf.20.1776223433385; Tue, 14 Apr 2026 20:23:53 -0700 (PDT) Received: from server0 (c-68-48-65-54.hsd1.mi.comcast.net. [68.48.65.54]) by smtp.gmail.com with ESMTPSA id d75a77b69052e-50e1b012b23sm3713071cf.30.2026.04.14.20.23.52 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 14 Apr 2026 20:23:52 -0700 (PDT) From: Michael Bommarito To: linux-usb@vger.kernel.org, Mika Westerberg Cc: Andreas Noever , Yehezkel Bernat , Greg Kroah-Hartman , linux-kernel@vger.kernel.org, stable@vger.kernel.org Subject: [PATCH 2/2] thunderbolt: test: add KUnit regression tests for XDomain property parser Date: Tue, 14 Apr 2026 23:23:35 -0400 Message-ID: <20260415032335.2826412-3-michael.bommarito@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260415032335.2826412-1-michael.bommarito@gmail.com> References: <20260415032335.2826412-1-michael.bommarito@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add three KUnit cases that exercise the defects fixed by the parent commit by feeding crafted XDomain property blocks to tb_property_parse_dir(): tb_test_property_parse_u32_wrap - entry->value =3D 0xFFFFFF00 and entry->length =3D 0x100 so their u32 sum 0x100000000 wraps to 0 under the block_len guard; without the fix the subsequent parse_dwdata() reads attacker-directed OOB memory. tb_test_property_parse_recursion - two DIRECTORY entries pointing at each other, driving __tb_property_parse_dir() recursion; without the fix the kernel stack is exhausted. tb_test_property_parse_dir_len_underflow - a DIRECTORY entry with length < 4 so non-root content_len =3D dir_len - 4 wraps size_t; without the fix nentries is huge and the entry walk runs OOB. Each test asserts tb_property_parse_dir() returns NULL on the crafted input. With CONFIG_KASAN=3Dy, running these on the pre-fix kernel reproduces an oops inside __tb_property_parse_dir (KASAN shadow-memory fault for the u32_wrap case, stack-guard trip for recursion, OOB read past block for dir_len underflow). Post-fix they pass cleanly. Run with: ./tools/testing/kunit/kunit.py run --arch=3Dx86_64 \\ --kconfig_add CONFIG_PCI=3Dy --kconfig_add CONFIG_NVMEM=3Dy \\ --kconfig_add CONFIG_USB4=3Dy --kconfig_add CONFIG_USB4_KUNIT_TEST=3Dy = \\ --kconfig_add CONFIG_KASAN=3Dy 'thunderbolt.tb_test_property_parse_*' Assisted-by: Claude:claude-opus-4-6 Assisted-by: Codex:gpt-5-4 Signed-off-by: Michael Bommarito --- drivers/thunderbolt/test.c | 127 +++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/drivers/thunderbolt/test.c b/drivers/thunderbolt/test.c index 1f4318249c22..22f4107fcb8d 100644 --- a/drivers/thunderbolt/test.c +++ b/drivers/thunderbolt/test.c @@ -2852,7 +2852,134 @@ static void tb_test_property_copy(struct kunit *tes= t) tb_property_free_dir(src); } =20 +/* + * Reproducers for three memory-safety defects in + * drivers/thunderbolt/property.c reached from a crafted XDomain + * PROPERTIES_RESPONSE payload. Without the fix these trip KASAN or + * smash the kernel stack; with the fix each returns NULL cleanly. + */ +static void tb_test_property_parse_u32_wrap(struct kunit *test) +{ + u32 *block =3D kunit_kzalloc(test, 500 * sizeof(u32), GFP_KERNEL); + struct tb_property_dir *dir; + struct { + u32 key_hi, key_lo; + u16 length; + u8 reserved; + u8 type; + u32 value; + } *e; + + /* Root header: magic + length=3D6 (single entry body of 4 dwords + + * 2 slack, keeps walk within block[]). */ + block[0] =3D 0x55584401; + block[1] =3D 6; + + /* Crafted DATA entry at block[2..5]: value =3D 0xFFFFFF00 and + * length =3D 0x100 are u32/u16 such that the u32 sum 0x100000000 + * wraps to 0, passing the sum <=3D block_len guard even though + * the real offset is block + 0xFFFFFF00 * 4 (~16 GiB past the + * block). The subsequent parse_dwdata() at property.c:132 + * copies entry->length*4 =3D 1024 bytes from that wild address + * into a fresh kcalloc buffer. + */ + e =3D (void *)&block[2]; + e->key_hi =3D 0x61616161; + e->key_lo =3D 0x61616161; + e->length =3D 0x100; + e->type =3D 0x64; /* TB_PROPERTY_TYPE_DATA */ + e->value =3D 0xFFFFFF00; + + dir =3D tb_property_parse_dir(block, 500); + /* With the fix this returns NULL; without it, KASAN splats in + * be32_to_cpu_array() / memcpy reading block + value*4 out of + * bounds. Assert on the safe outcome: a NULL dir. */ + KUNIT_EXPECT_NULL(test, dir); + tb_property_free_dir(dir); +} + +static void tb_test_property_parse_recursion(struct kunit *test) +{ + u32 *block =3D kunit_kzalloc(test, 500 * sizeof(u32), GFP_KERNEL); + struct tb_property_dir *dir; + struct entry { + u32 key_hi, key_lo; + u16 length; + u8 reserved; + u8 type; + u32 value; + } *e, *child_e; + + block[0] =3D 0x55584401; + block[1] =3D 4; /* rootdir length =3D one entry */ + + /* DIRECTORY entry pointing at dir_offset=3D2 with length=3D16. + * When parsed as non-root: content_offset =3D 6, content_len =3D 12, + * nentries =3D 3. The child's first entry at block[6] is also + * DIRECTORY pointing at 2, so the recursion oscillates between + * two dir_offsets until the kernel stack is exhausted. + */ + e =3D (void *)&block[2]; + e->key_hi =3D 0x61616161; + e->key_lo =3D 0x61616161; + e->length =3D 16; + e->type =3D 0x44; /* TB_PROPERTY_TYPE_DIRECTORY */ + e->value =3D 2; + + child_e =3D (void *)&block[6]; + child_e->key_hi =3D 0x62626262; + child_e->key_lo =3D 0x62626262; + child_e->length =3D 16; + child_e->type =3D 0x44; + child_e->value =3D 2; + + dir =3D tb_property_parse_dir(block, 500); + /* With the fix this returns NULL at TB_PROPERTY_MAX_DEPTH (8). + * Without it, the kernel stack-guard fires ~50-80 frames in + * and the kunit thread oopses. */ + KUNIT_EXPECT_NULL(test, dir); + tb_property_free_dir(dir); +} + +static void tb_test_property_parse_dir_len_underflow(struct kunit *test) +{ + u32 *block =3D kunit_kzalloc(test, 500 * sizeof(u32), GFP_KERNEL); + struct tb_property_dir *dir; + struct entry { + u32 key_hi, key_lo; + u16 length; + u8 reserved; + u8 type; + u32 value; + } *e; + + block[0] =3D 0x55584401; + block[1] =3D 4; + + /* DIRECTORY entry with length=3D3. When parsed as non-root, + * content_len =3D dir_len - 4 underflows size_t to ~SIZE_MAX, + * nentries =3D SIZE_MAX/4. The for-loop walks entries past the + * block, reading OOB on each iteration. + */ + e =3D (void *)&block[2]; + e->key_hi =3D 0x61616161; + e->key_lo =3D 0x61616161; + e->length =3D 3; + e->type =3D 0x44; + e->value =3D 6; + + dir =3D tb_property_parse_dir(block, 500); + /* With the fix: NULL. Without: KASAN splat on + * block[content_offset + i*4] for i > 124 (past the 500-dword + * block). */ + KUNIT_EXPECT_NULL(test, dir); + tb_property_free_dir(dir); +} + static struct kunit_case tb_test_cases[] =3D { + KUNIT_CASE(tb_test_property_parse_u32_wrap), + KUNIT_CASE(tb_test_property_parse_recursion), + KUNIT_CASE(tb_test_property_parse_dir_len_underflow), KUNIT_CASE(tb_test_path_basic), KUNIT_CASE(tb_test_path_not_connected_walk), KUNIT_CASE(tb_test_path_single_hop_walk), --=20 2.53.0