From nobody Mon Jun 8 05:24:53 2026 Received: from mail-qk1-f180.google.com (mail-qk1-f180.google.com [209.85.222.180]) (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 6CF6D37D124 for ; Fri, 5 Jun 2026 18:04:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.222.180 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780682672; cv=none; b=JGsVorSFomOjCrrVlVpZ+loEniAUmSInfwZC9S1LhOE6f8mvaFjzzV9i/z8sIXD2LiwO7g1hSb4XdKfUQBzh0gUlN0Xp0N1I4rnk7QX8hrDcJniMRqqBWTG8woCgVvqDrUCdlGU/3pwKk5hCnBrRVniLacizllxA7X1ZOXEzavc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780682672; c=relaxed/simple; bh=w/CsC5EWueF8gL1xQunrQPoTd4ZoWE/lsFrbWiEH6aY=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=poJFOeJ+/bn8Iw64VqgT1wXUFn4vsdagSShJs2qkk+Um6lmay72nXi52gRB8lraDIOE6pXfDp85Zd3nSNMFEZd3h2xh0E1pUSbWeiOgmzFVIs5z5WSHoR2EBnrIJlrSkzIx1QOGxA/pDOLTLleSWZJrlpJHGkmjUKs6P5mfeoms= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=trailofbits.com; spf=pass smtp.mailfrom=trailofbits.com; dkim=pass (2048-bit key) header.d=trailofbits.com header.i=@trailofbits.com header.b=hfGVVrBp; arc=none smtp.client-ip=209.85.222.180 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=trailofbits.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=trailofbits.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=trailofbits.com header.i=@trailofbits.com header.b="hfGVVrBp" Received: by mail-qk1-f180.google.com with SMTP id af79cd13be357-91574384cc2so260091585a.2 for ; Fri, 05 Jun 2026 11:04:31 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=trailofbits.com; s=google; t=1780682670; x=1781287470; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=9Gzd6lF0V2XoXPzNN3T098fiqnqBn4d7pV5tnTL7Pug=; b=hfGVVrBps/eA5UWiWKasPtjUBQJ4E0wlwVlkOTE/86Cej3kFI869U1cm6va8l7bRhr sMZ8GOI1R1GTRwd1P/810vT3xKixfGKpBoiIjmWVjx+yIWX7S2qTn53G8xgnNDIvCyJ3 BVT3AkfLRozs/siO9WdLTPascuI0MbHBL9f2LU2O3p7hNJ4MRVypSqkqYgwfqjqA1sts vhc/2ApeFN6GmZ8VYQ/YqhUpMrqm9qnGma9sGf2N/6a/LI125kytWLVrQQ6oPT1qEryL /aARw9lnL2UaHQl+Ipm1x2loQx7DbGCf+sunKtTpWxVJaRjy3oW2yqNetrDuGTpXpJU5 b+hg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780682670; x=1781287470; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=9Gzd6lF0V2XoXPzNN3T098fiqnqBn4d7pV5tnTL7Pug=; b=PEJkwtWn1d1Li9nfwVmVIkQSFz06fsNCVPkKyUQFS93fCbiDrbeFtpS9lYfcoEy1Ur /K94IbEsT8q/ciBmGA0zBEUMh9fs+uuCf7QqWT2cClxt4GqM1e400GLYGps/diAzdkyG 0VBKLGvdpL9gpzx/E//gayh7QJ3j3Qy4RE8eDVTBDi8vtR5DSjBElYY1cyhQrojEj0vK MEfnQBzpIClak+k4W5x8Ha/0wrhRzC2rGUShhvauAzves/LoJx8bvMUrLTwgzCXcf5Xn 5pnu/Zm7mf9NsneK52wWo0uakcZddQOJXOLHSgoVzpU0TkNEs2TgXduKTkkeZAk2CCLQ rZ4w== X-Forwarded-Encrypted: i=1; AFNElJ/Cb3ZbeFjseNFDjxaDliYcOJY3aWTNzFrnHE3q+ufY1yk7vLfLvjGTJuy0kJaPAelUGAZJHNVEARHoG8c=@vger.kernel.org X-Gm-Message-State: AOJu0YxV0mNXl97DPXpE/EsQf+DStJzVqqnTqhGli7kXn8zAlSVvogha 1CjbMCeeOtfCa2cBduisJ1QW+jTS/jfVB+QNP7m/pPu+Q52monoNmfw9gwLZy7UfcztvgQW6+0Z YSo1+v1s= X-Gm-Gg: Acq92OHzErWcoE3Kb1C18Uj8tmmJLOSbrUyS0v3Z4cxW7GlNdqdZCPS4b5tZX/4PugL YzFD7Qkjx+phKDgsMP+4nM+q/D9EpFx5Q9FOhj8dWBfurLbZxICOz7yqU6TYmz2wIVp15+BdDa6 cN+ClGxBv1wnKl4RFpVxBeqXRoKOOnL9fz2xCTwl7mpp9zc7pIzM9u2aVBETJutwpoN1PCLoAad JBFTha2hO9t65Zczf/y14jrOFaZHV78nmbeHFDec+mMUoRxOUhbI/mBcSUfZobxUYMXGUkOEC54 Tkd6sfU11b/7vMnBZblj+dciNgf9uVk/VpkM+3SKqQ17z9Tz26CwcV21ByGarzSTDFyy8r4pkDP ELSHTtVvsLUxCmkJdwXHr869RZZkaVzHzYBgbiFSb4Ul/nMhBvjlGhFv8Qrmfn8bO4ZQQycK8RG RppVpYUsMwGq0Ca/EE9Fyu4yb9XU3DgRJMiQqfHA== X-Received: by 2002:ac8:5814:0:b0:517:8da1:4d3 with SMTP id d75a77b69052e-51795afdceamr69746091cf.22.1780682670009; Fri, 05 Jun 2026 11:04:30 -0700 (PDT) Received: from localhost ([161.35.96.86]) by smtp.gmail.com with UTF8SMTPSA id d75a77b69052e-51775dbd2c9sm87282541cf.23.2026.06.05.11.04.28 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Fri, 05 Jun 2026 11:04:29 -0700 (PDT) From: Samuel Moelius To: Mikulas Patocka Cc: Samuel Moelius , linux-kernel@vger.kernel.org (open list) Subject: [PATCH] hpfs: validate external EA record lengths Date: Fri, 5 Jun 2026 18:04:23 +0000 Message-ID: <20260605180424.2453858-1-sam.moelius@trailofbits.com> X-Mailer: git-send-email 2.43.0 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" HPFS stores the length of the external extended-attribute list in fnode->ea_size_l, but the external EA walkers only check that four bytes of header remain before trusting the record's namelen and valuelen fields. A crafted image can declare an external EA list of four bytes while the first record header asks for a much longer name/value area. Reject records whose computed size does not fit in the declared external EA list before reading or advancing over the variable-length fields. Also reject indirect EA headers whose value length is not the required 8-byte sector/length payload before reading that payload. Keep the external EA list length unsigned while walking it so the on-disk u32 length is not narrowed into a signed int before these bounds checks. Assisted-by: Codex:gpt-5.5-cyber-preview Signed-off-by: Samuel Moelius --- fs/hpfs/ea.c | 57 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 8 deletions(-) diff --git a/fs/hpfs/ea.c b/fs/hpfs/ea.c index 4664f9ab06ee..ecd630604444 100644 --- a/fs/hpfs/ea.c +++ b/fs/hpfs/ea.c @@ -9,6 +9,29 @@ =20 #include "hpfs_fn.h" =20 +static bool hpfs_ea_ext_fits(struct super_block *s, secno a, int ano, + unsigned int pos, unsigned int len, + unsigned int size) +{ + if (pos > len || size > len - pos) { + hpfs_error(s, "EA record doesn't fit, %s %08x, pos %08x, len %08x", + ano ? "anode" : "sectors", a, pos, len); + return false; + } + return true; +} + +static bool hpfs_ea_indirect_ok(struct super_block *s, secno a, int ano, + unsigned int pos, struct extended_attribute *ea) +{ + if (ea_indirect(ea) && ea_valuelen(ea) !=3D 8) { + hpfs_error(s, "ea_indirect(ea) set while ea->valuelen!=3D8, %s %08x, pos= %08x", + ano ? "anode" : "sectors", a, pos); + return false; + } + return true; +} + /* Remove external extended attributes. ano specifies whether a is a=20 direct sector where eas starts or an anode */ =20 @@ -24,12 +47,12 @@ void hpfs_ea_ext_remove(struct super_block *s, secno a,= int ano, unsigned len) return; } if (hpfs_ea_read(s, a, ano, pos, 4, ex)) return; + if (!hpfs_ea_ext_fits(s, a, ano, pos, len, + 5 + ea->namelen + ea_valuelen(ea))) + return; + if (!hpfs_ea_indirect_ok(s, a, ano, pos, ea)) + return; if (ea_indirect(ea)) { - if (ea_valuelen(ea) !=3D 8) { - hpfs_error(s, "ea_indirect(ea) set while ea->valuelen!=3D8, %s %08x, p= os %08x", - ano ? "anode" : "sectors", a, pos); - return; - } if (hpfs_ea_read(s, a, ano, pos + 4, ea->namelen + 9, ex+4)) return; hpfs_ea_remove(s, ea_sec(ea), ea_in_anode(ea), ea_len(ea)); @@ -75,7 +98,8 @@ int hpfs_read_ea(struct super_block *s, struct fnode *fno= de, char *key, char *buf, int size) { unsigned pos; - int ano, len; + unsigned int len; + int ano; secno a; char ex[4 + 255 + 1 + 8]; struct extended_attribute *ea; @@ -102,6 +126,11 @@ int hpfs_read_ea(struct super_block *s, struct fnode *= fnode, char *key, return -EIO; } if (hpfs_ea_read(s, a, ano, pos, 4, ex)) return -EIO; + if (!hpfs_ea_ext_fits(s, a, ano, pos, len, + 5 + ea->namelen + ea_valuelen(ea))) + return -EIO; + if (!hpfs_ea_indirect_ok(s, a, ano, pos, ea)) + return -EIO; if (hpfs_ea_read(s, a, ano, pos + 4, ea->namelen + 1 + (ea_indirect(ea) = ? 8 : 0), ex + 4)) return -EIO; if (!strcmp(ea->name, key)) { @@ -131,7 +160,8 @@ char *hpfs_get_ea(struct super_block *s, struct fnode *= fnode, char *key, int *si { char *ret; unsigned pos; - int ano, len; + unsigned int len; + int ano; secno a; struct extended_attribute *ea; struct extended_attribute *ea_end =3D fnode_end_ea(fnode); @@ -160,6 +190,11 @@ char *hpfs_get_ea(struct super_block *s, struct fnode = *fnode, char *key, int *si return NULL; } if (hpfs_ea_read(s, a, ano, pos, 4, ex)) return NULL; + if (!hpfs_ea_ext_fits(s, a, ano, pos, len, + 5 + ea->namelen + ea_valuelen(ea))) + return NULL; + if (!hpfs_ea_indirect_ok(s, a, ano, pos, ea)) + return NULL; if (hpfs_ea_read(s, a, ano, pos + 4, ea->namelen + 1 + (ea_indirect(ea) = ? 8 : 0), ex + 4)) return NULL; if (!strcmp(ea->name, key)) { @@ -193,7 +228,8 @@ void hpfs_set_ea(struct inode *inode, struct fnode *fno= de, const char *key, fnode_secno fno =3D inode->i_ino; struct super_block *s =3D inode->i_sb; unsigned pos; - int ano, len; + unsigned int len; + int ano; secno a; unsigned char h[4]; struct extended_attribute *ea; @@ -221,6 +257,11 @@ void hpfs_set_ea(struct inode *inode, struct fnode *fn= ode, const char *key, return; } if (hpfs_ea_read(s, a, ano, pos, 4, ex)) return; + if (!hpfs_ea_ext_fits(s, a, ano, pos, len, + 5 + ea->namelen + ea_valuelen(ea))) + return; + if (!hpfs_ea_indirect_ok(s, a, ano, pos, ea)) + return; if (hpfs_ea_read(s, a, ano, pos + 4, ea->namelen + 1 + (ea_indirect(ea) = ? 8 : 0), ex + 4)) return; if (!strcmp(ea->name, key)) { --=20 2.43.0