From nobody Thu Oct 2 14:21:31 2025 Received: from mail-wm1-f41.google.com (mail-wm1-f41.google.com [209.85.128.41]) (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 8ADD7213E6A for ; Tue, 16 Sep 2025 09:01:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.41 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758013283; cv=none; b=XvboLE+gebKCBWCkmhZZIK0782wohoEeQ7LWFv46LhZ6GNgQuJc+usnQRkVxAr/gCPPCvFbbeL5VdFvUq6BQBTb2BOiJw+6Jez1Luaot+g4O45jDKWgm20Grhz25SZSR72nD94lJSMKNu9zta3I0ETkuO8uFG+O5GkmnByDg2qg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758013283; c=relaxed/simple; bh=qJDnwsi8rYP5Wp+H8LxkPOvT+aBCJa1jnIBCRmNCEj4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Hbt8YLTeVbIL5CfjQye2VSySfN97Pm3yrElgU/vkXL1v89gGqS3gNDt3RXi+r24AArIlO4nf2N85VA+MZYuMOI/ZTD8Cxk7EA45/Zb1zer2JhlulqRr1KMOObDmCLrbrwsNTeqlON4AROFvXIXAufBsOVufOGeMg7bXWCh1fWnk= 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=AbjscKHF; arc=none smtp.client-ip=209.85.128.41 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="AbjscKHF" Received: by mail-wm1-f41.google.com with SMTP id 5b1f17b1804b1-45f2b062b86so15316275e9.1 for ; Tue, 16 Sep 2025 02:01:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1758013280; x=1758618080; 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=2yrCmffzlGuEExOPu+2JuPwFc40pU2sHK0rUuIT3g74=; b=AbjscKHFL8ivpiE4yWO+py7ajRN2ClTsfiDFIRjyAWoo9kIt4V+jaLQmEtXoTMVOoU rrt8EjbnVxENPm5Fi2UHy3/PUzGC3BUeNun/bglZzVRhHIOPWHgmgBIaHl06VqL4S4Rm aPtBklFN924h2JgG7ULc8v5G4BNM+BPurRoQ2azY6NBh+CFMD0rbNoa+FPXInbSZPRZe T+DxzhyzxzX9y4nlWTB4nH/tfnphWTV+1VhiJs5kI3szySOS+f3870jwMy/DKI4YCWb3 9QL2EzjFAb2cQc8SIMZ6rKixzpInrUj/aNN+Hh5csqoziUUtZeLgijcFRrKXSeS8KGmc dtQw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1758013280; x=1758618080; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=2yrCmffzlGuEExOPu+2JuPwFc40pU2sHK0rUuIT3g74=; b=GbiJX/2Bm2HW2k4Hqg/F0DI8+cAnbQGocRsBjuW42rPtfo7LSJHxBfrTqGxXLQ4psf 7zpKIpdvZEpE9lZ70L8rgaNIZVh/P5JZEDHSwoL2CTHtIvJqO0pOKao57RFOIDv8dE1T ha6OpHsqQlWN+bNty9w1d00dg4lZYDGBU7THI3BRffonxDh+IPL6YC6dLjUo7bvd4s6K vkEpsZRGd0ZXziFYWa27Dbltp83mYOpZQiai49KAlccATHR3tQzy7nkR8R+r8guBqCw8 /zoW5WK+ypBMnZX+Sc8/LgtcG30/k1h2DUkA/K3wbhZkRrMBXcetVqxCPOOrbPDbM3LT Sf7g== X-Forwarded-Encrypted: i=1; AJvYcCXlK07LO9LtsODD8Jurb2XoWL3qgkkrTTWnBext2mExmJWZaNvcj7DFakuKewx/xLWtPG9VIGa7lWixx9E=@vger.kernel.org X-Gm-Message-State: AOJu0Yw3pIkm+T1+chsEgeY0nhVXZSbGdnUihYjYyJvujrmeXXs0lW4Q lDvx0zKmlE0RSN6coSre9lxzbPGAHweAlyUlvK22Jt56RD2ml6lq01x9 X-Gm-Gg: ASbGncsYt1KU1bf1SFZ0w8YLS/8ZwzAIiNDQVF5Si4abDT3r5VsXoNlGQzEw3/t3Sjp hOxX5TxEl/PrqwlTgA7V0TdmIHunccT18bXAxp+BeWkRlj6H4ReY9Znzpd6sUN115lobeKq/Fgd OV1hXhFRwcReIfhie42EXs6BEbNoFvrUSWkHljIhSYfJ+iT9moISVl2KWqpkLJ4/33wQqVTqbcK gppfwWkD/Pi1L6FDCPpJoEX3EXzan4ttpoQJVlFjL6nwBvSyOvGUQz/J0ZgXevS8/kdAZVSrwgV aPamiwrDg0lPAOMUMZ+gywkt9vIucnQSjVp6nV9LgCCe4pQx6DuQio09/XfK2YtZdBd/XCOOcNl rqjDZcO0cjdpThkGsZmWYhlnZ7wmCdWujtQc57/om6KMEyHxsVeMwwiH4aGtzP+9dTg2tE87736 RGLItQCs2/+JyDUadJojnspHM= X-Google-Smtp-Source: AGHT+IG5iZ2HWUjjmWFwCC/GHnLa6bpp4pAlSd7iyIhSqBHOTc39tKRs7gflZ9w5k6y0UE7LS5O+zA== X-Received: by 2002:a05:6000:250e:b0:3ec:d789:b35e with SMTP id ffacd0b85a97d-3ecd789b5b1mr817634f8f.8.1758013279529; Tue, 16 Sep 2025 02:01:19 -0700 (PDT) Received: from xl-nested.c.googlers.com.com (42.16.79.34.bc.googleusercontent.com. [34.79.16.42]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-45e037186e5sm212975035e9.5.2025.09.16.02.01.18 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 16 Sep 2025 02:01:18 -0700 (PDT) From: Ethan Graham To: ethangraham@google.com, glider@google.com Cc: andreyknvl@gmail.com, andy@kernel.org, brauner@kernel.org, brendan.higgins@linux.dev, davem@davemloft.net, davidgow@google.com, dhowells@redhat.com, dvyukov@google.com, elver@google.com, herbert@gondor.apana.org.au, ignat@cloudflare.com, jack@suse.cz, jannh@google.com, johannes@sipsolutions.net, kasan-dev@googlegroups.com, kees@kernel.org, kunit-dev@googlegroups.com, linux-crypto@vger.kernel.org, linux-kernel@vger.kernel.org, linux-mm@kvack.org, lukas@wunner.de, rmoar@google.com, shuah@kernel.org, tarasmadan@google.com Subject: [PATCH v1 01/10] mm/kasan: implement kasan_poison_range Date: Tue, 16 Sep 2025 09:01:00 +0000 Message-ID: <20250916090109.91132-2-ethan.w.s.graham@gmail.com> X-Mailer: git-send-email 2.51.0.384.g4c02a37b29-goog In-Reply-To: <20250916090109.91132-1-ethan.w.s.graham@gmail.com> References: <20250916090109.91132-1-ethan.w.s.graham@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" From: Ethan Graham Introduce a new helper function, kasan_poison_range(), to encapsulate the logic for poisoning an arbitrary memory range of a given size, and expose it publically in . This is a preparatory change for the upcoming KFuzzTest patches, which requires the ability to poison the inter-region padding in its input buffers. No functional change to any other subsystem is intended by this commit. Reviewed-by: Alexander Potapenko --- v3: - Enforce KASAN_GRANULE_SIZE alignment for the end of the range in kasan_poison_range(), and return -EINVAL when this isn't respected. --- Signed-off-by: Ethan Graham --- include/linux/kasan.h | 11 +++++++++++ mm/kasan/shadow.c | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/include/linux/kasan.h b/include/linux/kasan.h index 890011071f2b..cd6cdf732378 100644 --- a/include/linux/kasan.h +++ b/include/linux/kasan.h @@ -102,6 +102,16 @@ static inline bool kasan_has_integrated_init(void) } =20 #ifdef CONFIG_KASAN + +/** + * kasan_poison_range - poison the memory range [@addr, @addr + @size) + * + * The exact behavior is subject to alignment with KASAN_GRANULE_SIZE, def= ined + * in : if @start is unaligned, the initial partial gran= ule + * at the beginning of the range is only poisoned if CONFIG_KASAN_GENERIC= =3Dy. + */ +int kasan_poison_range(const void *addr, size_t size); + void __kasan_unpoison_range(const void *addr, size_t size); static __always_inline void kasan_unpoison_range(const void *addr, size_t = size) { @@ -402,6 +412,7 @@ static __always_inline bool kasan_check_byte(const void= *addr) =20 #else /* CONFIG_KASAN */ =20 +static inline int kasan_poison_range(const void *start, size_t size) { ret= urn 0; } static inline void kasan_unpoison_range(const void *address, size_t size) = {} static inline void kasan_poison_pages(struct page *page, unsigned int orde= r, bool init) {} diff --git a/mm/kasan/shadow.c b/mm/kasan/shadow.c index d2c70cd2afb1..7faed02264f2 100644 --- a/mm/kasan/shadow.c +++ b/mm/kasan/shadow.c @@ -147,6 +147,40 @@ void kasan_poison(const void *addr, size_t size, u8 va= lue, bool init) } EXPORT_SYMBOL_GPL(kasan_poison); =20 +int kasan_poison_range(const void *addr, size_t size) +{ + uintptr_t start_addr =3D (uintptr_t)addr; + uintptr_t head_granule_start; + uintptr_t poison_body_start; + uintptr_t poison_body_end; + size_t head_prefix_size; + uintptr_t end_addr; + + if ((start_addr + size) % KASAN_GRANULE_SIZE) + return -EINVAL; + + end_addr =3D ALIGN_DOWN(start_addr + size, KASAN_GRANULE_SIZE); + if (start_addr >=3D end_addr) + return -EINVAL; + + head_granule_start =3D ALIGN_DOWN(start_addr, KASAN_GRANULE_SIZE); + head_prefix_size =3D start_addr - head_granule_start; + + if (IS_ENABLED(CONFIG_KASAN_GENERIC) && head_prefix_size > 0) + kasan_poison_last_granule((void *)head_granule_start, + head_prefix_size); + + poison_body_start =3D ALIGN(start_addr, KASAN_GRANULE_SIZE); + poison_body_end =3D ALIGN_DOWN(end_addr, KASAN_GRANULE_SIZE); + + if (poison_body_start < poison_body_end) + kasan_poison((void *)poison_body_start, + poison_body_end - poison_body_start, + KASAN_SLAB_REDZONE, false); + return 0; +} +EXPORT_SYMBOL(kasan_poison_range); + #ifdef CONFIG_KASAN_GENERIC void kasan_poison_last_granule(const void *addr, size_t size) { --=20 2.51.0.384.g4c02a37b29-goog From nobody Thu Oct 2 14:21:31 2025 Received: from mail-wm1-f51.google.com (mail-wm1-f51.google.com [209.85.128.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 E96AE284693 for ; Tue, 16 Sep 2025 09:01:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.51 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758013285; cv=none; b=k/hKJpMAo7l4FyfTyEOxJi0sCjElBxtoDClcG2zuIEHc6eWcpY3++4dslB0z+0BxQ14WH9ByGdNm8+wx2ub+AI5uzBP07YO9BRI/N/N0H6wtqBO7/EcI+i720esuPU2Ah5/Pjz/MZ7/QbvH4JZ85u1MS1ND6ovR1uL9UtV65b2U= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758013285; c=relaxed/simple; bh=rHdXcW4hedzb4INGLcyaJMDJVb1fKtNcoMkamsRR8fA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=nWT0QjcNiIgLOLgxLtK9tN2w6gmdgHAat6fpX72qdVqNH9mqcI9vc/i2PJTuDG4WE/FjVPJqHbJXlufpVzAuePgioWiyUdjTeKXtzIb2Aax7HcpNoHeZYRAMmZDK114iuwxxa2w87QlEidrEXHwHt+Q/YYt5bnGNoyoZFQcZvog= 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=iZhEbVx/; arc=none smtp.client-ip=209.85.128.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="iZhEbVx/" Received: by mail-wm1-f51.google.com with SMTP id 5b1f17b1804b1-45dd505a1dfso41103055e9.2 for ; Tue, 16 Sep 2025 02:01:22 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1758013281; x=1758618081; 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=fs/Mkvp1THxRdctoJn4ZALLS8uDDfar/BAPXGx8Q6Zo=; b=iZhEbVx/gw9x8hm2gPPJ6OEudM0fkNsIefpEZ4R7/tFk3/0dXr4uU7q3KpPylMVok2 uM8XHTMr+xjzIiMP1qHb5GmvaDNT1RA9DV/egloI9IqEi2c1GSkmZJ3hCFHTJHgymYm2 13vDGaGWKJxvLVQVOb1MibJn61EKXqNmIEeza+Wy/qd64wVp1pb07l4G4jsQp6SRQNb0 YiItijAFTlDXSemu9KD4X0pa8JhG/3W2C7aLhM5mATIRkbJnkYCGdhmD1Dnzy//42oF1 1d5gwI0U6eNSOJ6ibJgFTPsw7/MvU5F9Bd8pTUB9VBQ0NFEsLbulijVOefAYarqmpxQO Qelg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1758013281; x=1758618081; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=fs/Mkvp1THxRdctoJn4ZALLS8uDDfar/BAPXGx8Q6Zo=; b=c9ZgnuCO9QGyrmuK8zp3zQxw8nzusf/sYHAeQepV4O4zDIMyybp8k/pKk/VqKYKjyA IpCnVh3oZEQFVg3sVvU7r70HxFD+URMKH65CDOHqWMXXkmRWPOrSBS/Li5R9uxOkF4Zv rHo+/Ct6doBkuRAu85DpTYzrhoDD3rDFZIUyP3YGe9nsdySzwVV0LsKf6Kq3x2AsLsCB Msjs1EueYB+Xyb3SJ3Av0YGwOGBY5q73FMw+76aPSDc9m7E2y64QrSOH18Fccxj7HjsM 1FdqObPJg+DC+1vga4gQCJXUMj5GYtc3OoWHgz/XQPaoCJx/llh4RbuCzQTqtbDuO3KG 8I8w== X-Forwarded-Encrypted: i=1; AJvYcCXAhtMXkF4mCK57MMu8VA+S9QQm/zfV9u4ALsvH/W1IrnHT3wSSytCwGamQMWqFChUdeQW/0pj7hi8vKCQ=@vger.kernel.org X-Gm-Message-State: AOJu0YweQEuWXredU3GVyBQkDHwZBUt+6ajJAedq+KmjhCJshg3MJP1v 2iMqZ0NCbNxM8zu1d5yOY/eOv3JudvzUpuk0u+PCGnlxFOP13pmtx5Sh X-Gm-Gg: ASbGnctJBdPIWDclaHWp74WsxMfcC6tWX6o9nrMLlxSkJCw5YNnnPj/m6UHNE1ysN1G Ogap+DpRw3jRrQfE96uo0EVF8QK+0JQJ20aNc3B3L7qbcF+9gPAsh/7OK6hd6lRY0jriRr6LW2/ IiDS9pb9kGyMjq7nbmylYPEcN2KYOA6+5IlVxdcmauyirtvHkB3jO2YI9vTLbnQRbtUj7iEwirK VPiyMJZZU2VxxlHidejzHwFWBjGwXVicp/eLNFUf/IbJQNX7E7JabBrfk5y+8Ng0i2RLeVB66q6 GAr/Zj/R3GrhVSaf7UgScf7M7jVETlavwdEphvdn4/xe3GpoK7kVeZkJ8JBEGzXFQB71c9K3ZCu 6HYg8Ak2fDauMZ84VNEpgY+xfrXIhmagY6jOpv81fv8QgD9rUPa5+AijTnT6LzzTmGIjfmC5Zji 2uTVvdsmaMNouDjS7jG0o+ksM= X-Google-Smtp-Source: AGHT+IHwQoGXKySunnNfE/yk4Lj1m+tend1dwCc17kn/aqDyL3pKFJsdM1voo4KacgQC1QzpZx8uDQ== X-Received: by 2002:a7b:c04b:0:b0:456:43c:dcdc with SMTP id 5b1f17b1804b1-45f212050femr102155175e9.33.1758013280425; Tue, 16 Sep 2025 02:01:20 -0700 (PDT) Received: from xl-nested.c.googlers.com.com (42.16.79.34.bc.googleusercontent.com. [34.79.16.42]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-45e037186e5sm212975035e9.5.2025.09.16.02.01.19 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 16 Sep 2025 02:01:19 -0700 (PDT) From: Ethan Graham To: ethangraham@google.com, glider@google.com Cc: andreyknvl@gmail.com, andy@kernel.org, brauner@kernel.org, brendan.higgins@linux.dev, davem@davemloft.net, davidgow@google.com, dhowells@redhat.com, dvyukov@google.com, elver@google.com, herbert@gondor.apana.org.au, ignat@cloudflare.com, jack@suse.cz, jannh@google.com, johannes@sipsolutions.net, kasan-dev@googlegroups.com, kees@kernel.org, kunit-dev@googlegroups.com, linux-crypto@vger.kernel.org, linux-kernel@vger.kernel.org, linux-mm@kvack.org, lukas@wunner.de, rmoar@google.com, shuah@kernel.org, tarasmadan@google.com Subject: [PATCH v1 02/10] kfuzztest: add user-facing API and data structures Date: Tue, 16 Sep 2025 09:01:01 +0000 Message-ID: <20250916090109.91132-3-ethan.w.s.graham@gmail.com> X-Mailer: git-send-email 2.51.0.384.g4c02a37b29-goog In-Reply-To: <20250916090109.91132-1-ethan.w.s.graham@gmail.com> References: <20250916090109.91132-1-ethan.w.s.graham@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" From: Ethan Graham Add the foundational user-facing components for the KFuzzTest framework. This includes the main API header , the Kconfig option to enable the feature, and the required linker script changes which introduce three new ELF sections in vmlinux. Note that KFuzzTest is intended strictly for debug builds only, and should never be enabled in a production build. The fact that it exposes internal kernel functions and state directly to userspace may constitute a serious security vulnerability if used for any reason other than testing. The header defines: - The FUZZ_TEST() macro for creating test targets. - The data structures required for the binary serialization format, which allows passing complex inputs from userspace. - The metadata structures for test targets, constraints and annotations, which are placed in dedicated ELF sections (.kfuzztest_*) for discovery. This patch only adds the public interface and build integration; no runtime logic is included. --- v3: - Move KFuzzTest metadata definitions to generic vmlinux linkage so that the framework isn't bound to x86_64. - Return -EFAULT when simple_write_to_buffer returns a value not equal to the input length in the main FUZZ_TEST macro. - Enforce a maximum input size of 64KiB in the main FUZZ_TEST macro, returning -EINVAL when it isn't respected. - Refactor KFUZZTEST_ANNOTATION_* macros. - Taint the kernel with TAINT_TEST inside the FUZZ_TEST macro when a fuzz target is invoked for the first time. --- Signed-off-by: Ethan Graham --- include/asm-generic/vmlinux.lds.h | 22 +- include/linux/kfuzztest.h | 494 ++++++++++++++++++++++++++++++ lib/Kconfig.debug | 1 + lib/kfuzztest/Kconfig | 20 ++ 4 files changed, 536 insertions(+), 1 deletion(-) create mode 100644 include/linux/kfuzztest.h create mode 100644 lib/kfuzztest/Kconfig diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinu= x.lds.h index ae2d2359b79e..9afe569d013b 100644 --- a/include/asm-generic/vmlinux.lds.h +++ b/include/asm-generic/vmlinux.lds.h @@ -373,7 +373,8 @@ defined(CONFIG_AUTOFDO_CLANG) || defined(CONFIG_PROPELL= ER_CLANG) TRACE_PRINTKS() \ BPF_RAW_TP() \ TRACEPOINT_STR() \ - KUNIT_TABLE() + KUNIT_TABLE() \ + KFUZZTEST_TABLE() =20 /* * Data section helpers @@ -966,6 +967,25 @@ defined(CONFIG_AUTOFDO_CLANG) || defined(CONFIG_PROPEL= LER_CLANG) BOUNDED_SECTION_POST_LABEL(.kunit_init_test_suites, \ __kunit_init_suites, _start, _end) =20 +#ifdef CONFIG_KFUZZTEST +#define KFUZZTEST_TABLE() \ + . =3D ALIGN(PAGE_SIZE); \ + __kfuzztest_targets_start =3D .; \ + KEEP(*(.kfuzztest_target)); \ + __kfuzztest_targets_end =3D .; \ + . =3D ALIGN(PAGE_SIZE); \ + __kfuzztest_constraints_start =3D .; \ + KEEP(*(.kfuzztest_constraint)); \ + __kfuzztest_constraints_end =3D .; \ + . =3D ALIGN(PAGE_SIZE); \ + __kfuzztest_annotations_start =3D .; \ + KEEP(*(.kfuzztest_annotation)); \ + __kfuzztest_annotations_end =3D .; + +#else /* CONFIG_KFUZZTEST */ +#define KFUZZTEST_TABLE() +#endif /* CONFIG_KFUZZTEST */ + #ifdef CONFIG_BLK_DEV_INITRD #define INIT_RAM_FS \ . =3D ALIGN(4); \ diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h new file mode 100644 index 000000000000..1e5ed517f291 --- /dev/null +++ b/include/linux/kfuzztest.h @@ -0,0 +1,494 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * The Kernel Fuzz Testing Framework (KFuzzTest) API for defining fuzz tar= gets + * for internal kernel functions. + * + * For more information please see Documentation/dev-tools/kfuzztest.rst. + * + * Copyright 2025 Google LLC + */ +#ifndef KFUZZTEST_H +#define KFUZZTEST_H + +#include +#include +#include + +#define KFUZZTEST_HEADER_MAGIC (0xBFACE) +#define KFUZZTEST_V0 (0) + +/** + * @brief The KFuzzTest Input Serialization Format + * + * KFuzzTest receives its input from userspace as a single binary blob. Th= is + * format allows for the serialization of complex, pointer-rich C structur= es + * into a flat buffer that can be safely passed into the kernel. This form= at + * requires only a single copy from userspace into a kernel buffer, and no + * further kernel allocations. Pointers are patched internally using a "re= gion" + * system where each region corresponds to some pointed-to data. + * + * Regions should be padded to respect alignment constraints of their unde= rlying + * types, and should be followed by at least 8 bytes of padding. These pad= ded + * regions are poisoned by KFuzzTest to ensure that KASAN catches OOB acce= sses. + * + * The format consists of a header and three main components: + * 1. An 8-byte header: Contains KFUZZTEST_MAGIC in the first 4 bytes, and= the + * version number in the subsequent 4 bytes. This ensures backwards + * compatibility in the event of future format changes. + * 2. A reloc_region_array: Defines the memory layout of the target struct= ure + * by partitioning the payload into logical regions. Each logical region + * should contain the byte representation of the type that it represents, + * including any necessary padding. The region descriptors should be + * ordered by offset ascending. + * 3. A reloc_table: Provides "linking" instructions that tell the kernel = how + * to patch pointer fields to point to the correct regions. By design, + * the first region (index 0) is passed as input into a FUZZ_TEST. + * 4. A Payload: The raw binary data for the target structure and its asso= ciated + * buffers. This should be aligned to the maximum alignment of all + * regions to satisfy alignment requirements of the input types, but this + * isn't checked by the parser. + * + * For a detailed specification of the binary layout see the full document= ation + * at: Documentation/dev-tools/kfuzztest.rst + */ + +/** + * struct reloc_region - single contiguous memory region in the payload + * + * @offset: The byte offset of this region from the start of the payload, = which + * should be aligned to the alignment requirements of the region's + * underlying type. + * @size: The size of this region in bytes. + */ +struct reloc_region { + uint32_t offset; + uint32_t size; +}; + +/** + * struct reloc_region_array - array of regions in an input + * + * @num_regions: The total number of regions defined. + * @regions: A flexible array of `num_regions` region descriptors. + */ +struct reloc_region_array { + uint32_t num_regions; + struct reloc_region regions[]; +}; + +/** + * struct reloc_entry - a single pointer to be patched in an input + * + * @region_id: The index of the region in the `reloc_region_array` that + * contains the pointer. + * @region_offset: The start offset of the pointer inside of the region. + * @value: contains the index of the pointee region, or KFUZZTEST_REGIONID= _NULL + * if the pointer is NULL. + */ +struct reloc_entry { + uint32_t region_id; + uint32_t region_offset; + uint32_t value; +}; + +/** + * struct reloc_entry - array of relocations required by an input + * + * @num_entries: the number of pointer relocations. + * @padding_size: the number of padded bytes between the last relocation in + * entries, and the start of the payload data. This should be at least + * 8 bytes, as it is used for poisoning. + * @entries: array of relocations. + */ +struct reloc_table { + uint32_t num_entries; + uint32_t padding_size; + struct reloc_entry entries[]; +}; + +/** + * kfuzztest_parse_and_relocate - validate and relocate a KFuzzTest input + * + * @input: A buffer containing the serialized input for a fuzz target. + * @input_size: the size in bytes of the @input buffer. + * @arg_ret: return pointer for the test case's input structure. + */ +int kfuzztest_parse_and_relocate(void *input, size_t input_size, void **ar= g_ret); + +/* + * Dump some information on the parsed headers and payload. Can be useful = for + * debugging inputs when writing an encoder for the KFuzzTest input format. + */ +__attribute__((unused)) static inline void kfuzztest_debug_header(struct r= eloc_region_array *regions, + struct reloc_table *rt, void *payload_start, + void *payload_end) +{ + uint32_t i; + + pr_info("regions: { num_regions =3D %u } @ %px", regions->num_regions, re= gions); + for (i =3D 0; i < regions->num_regions; i++) { + pr_info(" region_%u: { start: 0x%x, size: 0x%x }", i, regions->regions[= i].offset, + regions->regions[i].size); + } + + pr_info("reloc_table: { num_entries =3D %u, padding =3D %u } @ offset 0x%= lx", rt->num_entries, rt->padding_size, + (char *)rt - (char *)regions); + for (i =3D 0; i < rt->num_entries; i++) { + pr_info(" reloc_%u: { src: %u, offset: 0x%x, dst: %u }", i, rt->entries= [i].region_id, + rt->entries[i].region_offset, rt->entries[i].value); + } + + pr_info("payload: [0x%lx, 0x%lx)", (char *)payload_start - (char *)region= s, + (char *)payload_end - (char *)regions); +} + +struct kfuzztest_target { + const char *name; + const char *arg_type_name; + ssize_t (*write_input_cb)(struct file *filp, const char __user *buf, size= _t len, loff_t *off); +} __aligned(32); + +#define KFUZZTEST_MAX_INPUT_SIZE (PAGE_SIZE * 16) + +/** + * FUZZ_TEST - defines a KFuzzTest target + * + * @test_name: The unique identifier for the fuzz test, which is used to n= ame + * the debugfs entry, e.g., /sys/kernel/debug/kfuzztest/@test_name. + * @test_arg_type: The struct type that defines the inputs for the test. T= his + * must be the full struct type (e.g., "struct my_inputs"), not a typedef. + * + * Context: + * This macro is the primary entry point for the KFuzzTest framework. It + * generates all the necessary boilerplate for a fuzz test, including: + * - A static `struct kfuzztest_target` instance that is placed in a + * dedicated ELF section for discovery by userspace tools. + * - A `debugfs` write callback that handles receiving serialized data f= rom + * a fuzzer, parsing it, and "hydrating" it into a valid C struct. + * - A function stub where the developer places the test logic. + * + * User-Provided Logic: + * The developer must provide the body of the fuzz test logic within the c= urly + * braces following the macro invocation. Within this scope, the framework + * provides the `arg` variable, which is a pointer of type `@test_arg_type= *`=20 + * to the fully hydrated input structure. All pointer fields within this s= truct + * have been relocated and are valid kernel pointers. This is the primary + * variable to use for accessing fuzzing inputs. + * + * Example Usage: + * + * // 1. The kernel function we want to fuzz. + * int process_data(const char *data, size_t len); + * + * // 2. Define a struct to hold all inputs for the function. + * struct process_data_inputs { + * const char *data; + * size_t len; + * }; + * + * // 3. Define the fuzz test using the FUZZ_TEST macro. + * FUZZ_TEST(process_data_fuzzer, struct process_data_inputs) + * { + * int ret; + * // Use KFUZZTEST_EXPECT_* to enforce preconditions. + * // The test will exit early if data is NULL. + * KFUZZTEST_EXPECT_NOT_NULL(process_data_inputs, data); + * + * // Use KFUZZTEST_ANNOTATE_* to provide hints to the fuzzer. + * // This links the 'len' field to the 'data' buffer. + * KFUZZTEST_ANNOTATE_LEN(process_data_inputs, len, data); + * + * // Call the function under test using the 'arg' variable. OOB memory + * // accesses will be caught by KASAN, but the user can also choose to + * // validate the return value and log any failures. + * ret =3D process_data(arg->data, arg->len); + * } + */ +#define FUZZ_TEST(test_name, test_arg_type) \ + static ssize_t kfuzztest_write_cb_##test_name(struct file *filp, const ch= ar __user *buf, size_t len, \ + loff_t *off); \ + static void kfuzztest_logic_##test_name(test_arg_type *arg); \ + const struct kfuzztest_target __fuzz_test__##test_name __section(".kfuzzt= est_target") __used =3D { \ + .name =3D #test_name, \ + .arg_type_name =3D #test_arg_type, \ + .write_input_cb =3D kfuzztest_write_cb_##test_name, \ + }; \ + static ssize_t kfuzztest_write_cb_##test_name(struct file *filp, const ch= ar __user *buf, size_t len, \ + loff_t *off) \ + { \ + test_arg_type *arg; \ + void *buffer; \ + int ret; \ + \ + /* + * Taint the kernel on the first fuzzing invocation. The debugfs + * interface provides a high-risk entry point for userspace to + * call kernel functions with untrusted input. + */ \ + if (!test_taint(TAINT_TEST)) \ + add_taint(TAINT_TEST, LOCKDEP_STILL_OK); \ + if (len >=3D KFUZZTEST_MAX_INPUT_SIZE) { \ + pr_warn(#test_name ": user input of size %zu is too large", len); \ + return -EINVAL; \ + } \ + buffer =3D kmalloc(len, GFP_KERNEL); \ + if (!buffer) \ + return -ENOMEM; \ + ret =3D simple_write_to_buffer(buffer, len, off, buf, len); \ + if (ret !=3D len){ \ + ret =3D -EFAULT; \ + goto out; \ + }; \ + ret =3D kfuzztest_parse_and_relocate(buffer, len, (void **)&arg); \ + if (ret < 0) \ + goto out; \ + kfuzztest_logic_##test_name(arg); \ + ret =3D len; \ +out: \ + kfree(buffer); \ + return ret; \ + } \ + static void kfuzztest_logic_##test_name(test_arg_type *arg) + +enum kfuzztest_constraint_type { + EXPECT_EQ, + EXPECT_NE, + EXPECT_LT, + EXPECT_LE, + EXPECT_GT, + EXPECT_GE, + EXPECT_IN_RANGE, +}; + +/** + * struct kfuzztest_constraint - a metadata record for a domain constraint + * + * Domain constraints are rules about the input data that must be satisfie= d for + * a fuzz test to proceed. While they are enforced in the kernel with a ru= ntime + * check, they are primarily intended as a discoverable contract for users= pace + * fuzzers. + * + * Instances of this struct are generated by the KFUZZTEST_EXPECT_* macros + * and placed into the read-only ".kfuzztest_constraint" ELF section of the + * vmlinux binary. A fuzzer can parse this section to learn about the + * constraints and generate valid inputs more intelligently. + * + * For an example of how these constraints are used within a fuzz test, se= e the + * documentation for the FUZZ_TEST() macro. + * + * @input_type: The name of the input struct type, without the leading + * "struct ". + * @field_name: The name of the field within the struct that this constrai= nt + * applies to. + * @value1: The primary value used in the comparison (e.g., the upper + * bound for EXPECT_LE). + * @value2: The secondary value, used only for multi-value comparisons + * (e.g., the upper bound for EXPECT_IN_RANGE). + * @type: The type of the constraint. + */ +struct kfuzztest_constraint { + const char *input_type; + const char *field_name; + uintptr_t value1; + uintptr_t value2; + enum kfuzztest_constraint_type type; +} __aligned(64); + +#define __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val1, val2, tpe, pr= edicate) \ + do { \ + static struct kfuzztest_constraint __constraint_##arg_type##_##field \ + __section(".kfuzztest_constraint") __used =3D { \ + .input_type =3D "struct " #arg_type, \ + .field_name =3D #field, \ + .value1 =3D (uintptr_t)val1, \ + .value2 =3D (uintptr_t)val2, \ + .type =3D tpe, \ + }; \ + if (!(predicate)) \ + return; \ + } while (0) + +/** + * KFUZZTEST_EXPECT_EQ - constrain a field to be equal to a value + * + * @arg_type: name of the input structure, without the leading "struct ". + * @field: some field that is comparable + * @val: a value of the same type as @arg_type.@field + */ +#define KFUZZTEST_EXPECT_EQ(arg_type, field, val) \ + __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_EQ, arg->= field =3D=3D val); + +/** + * KFUZZTEST_EXPECT_NE - constrain a field to be not equal to a value + * + * @arg_type: name of the input structure, without the leading "struct ". + * @field: some field that is comparable. + * @val: a value of the same type as @arg_type.@field. + */ +#define KFUZZTEST_EXPECT_NE(arg_type, field, val) \ + __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_NE, arg->= field !=3D val); + +/** + * KFUZZTEST_EXPECT_LT - constrain a field to be less than a value + * + * @arg_type: name of the input structure, without the leading "struct ". + * @field: some field that is comparable. + * @val: a value of the same type as @arg_type.@field. + */ +#define KFUZZTEST_EXPECT_LT(arg_type, field, val) \ + __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_LT, arg->= field < val); + +/** + * KFUZZTEST_EXPECT_LE - constrain a field to be less than or equal to a v= alue + * + * @arg_type: name of the input structure, without the leading "struct ". + * @field: some field that is comparable. + * @val: a value of the same type as @arg_type.@field. + */ +#define KFUZZTEST_EXPECT_LE(arg_type, field, val) \ + __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_LE, arg-= >field <=3D val); + +/** + * KFUZZTEST_EXPECT_GT - constrain a field to be greater than a value + * + * @arg_type: name of the input structure, without the leading "struct ". + * @field: some field that is comparable. + * @val: a value of the same type as @arg_type.@field. + */ +#define KFUZZTEST_EXPECT_GT(arg_type, field, val) \ + __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_GT, arg->= field > val); + +/** + * KFUZZTEST_EXPECT_GE - constrain a field to be greater than or equal to = a value + * + * @arg_type: name of the input structure, without the leading "struct ". + * @field: some field that is comparable. + * @val: a value of the same type as @arg_type.@field. + */ +#define KFUZZTEST_EXPECT_GE(arg_type, field, val) \ + __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_GE, arg->= field >=3D val); + +/** + * KFUZZTEST_EXPECT_GE - constrain a pointer field to be non-NULL + * + * @arg_type: name of the input structure, without the leading "struct ". + * @field: some field that is comparable. + * @val: a value of the same type as @arg_type.@field. + */ +#define KFUZZTEST_EXPECT_NOT_NULL(arg_type, field) KFUZZTEST_EXPECT_NE(arg= _type, field, NULL) + +/** + * KFUZZTEST_EXPECT_IN_RANGE - constrain a field to be within a range + * + * @arg_type: name of the input structure, without the leading "struct ". + * @field: some field that is comparable. + * @lower_bound: a lower bound of the same type as @arg_type.@field. + * @upper_bound: an upper bound of the same type as @arg_type.@field. + */ +#define KFUZZTEST_EXPECT_IN_RANGE(arg_type, field, lower_bound, upper_boun= d) \ + __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, lower_bound, upper_bound, \ + EXPECT_IN_RANGE, arg->field >=3D lower_bound && arg->field <=3D upper_b= ound); + +/** + * Annotations express attributes about structure fields that can't be eas= ily + * or safely verified at runtime. They are intended as hints to the fuzzing + * engine to help it generate more semantically correct and effective inpu= ts. + * Unlike constraints, annotations do not add any runtime checks and do not + * cause a test to exit early. + * + * For example, a `char *` field could be a raw byte buffer or a C-style + * null-terminated string. A fuzzer that is aware of this distinction can = avoid + * creating inputs that would cause trivial, uninteresting crashes from re= ading + * past the end of a non-null-terminated buffer. + */ +enum kfuzztest_annotation_attribute { + ATTRIBUTE_LEN, + ATTRIBUTE_STRING, + ATTRIBUTE_ARRAY, +}; + +/** + * struct kfuzztest_annotation - a metadata record for a fuzzer hint + * + * This struct captures a single hint about a field in the input structure. + * Instances are generated by the KFUZZTEST_ANNOTATE_* macros and are plac= ed + * into the read-only ".kfuzztest_annotation" ELF section of the vmlinux b= inary. + * + * A userspace fuzzer can parse this section to understand the semantic + * relationships between fields (e.g., which field is a length for which + * buffer) and the expected format of the data (e.g., a null-terminated + * string). This allows the fuzzer to be much more intelligent during input + * generation and mutation. + * + * For an example of how annotations are used within a fuzz test, see the + * documentation for the FUZZ_TEST() macro. + * + * @input_type: The name of the input struct type. + * @field_name: The name of the field being annotated (e.g., the data + * buffer field). + * @linked_field_name: For annotations that link two fields (like + * ATTRIBUTE_LEN), this is the name of the related field (e.g., the + * length field). For others, this may be unused. + * @attrib: The type of the annotation hint. + */ +struct kfuzztest_annotation { + const char *input_type; + const char *field_name; + const char *linked_field_name; + enum kfuzztest_annotation_attribute attrib; +} __aligned(32); + +#define __KFUZZTEST_ANNOTATE(arg_type, field, linked_field, attribute) = \ + static struct kfuzztest_annotation __annotation_##arg_type##_##field __se= ction(".kfuzztest_annotation") \ + __used =3D { \ + .input_type =3D "struct " #arg_type, \ + .field_name =3D #field, \ + .linked_field_name =3D #linked_field, \ + .attrib =3D attribute, \ + } + +/** + * KFUZZTEST_ANNOTATE_STRING - annotate a char* field as a C string + * + * We define a C string as a sequence of non-zero characters followed by e= xactly + * one null terminator. + * + * @arg_type: name of the input structure, without the leading "struct ". + * @field: the name of the field to annotate. + */ +#define KFUZZTEST_ANNOTATE_STRING(arg_type, field) __KFUZZTEST_ANNOTATE(ar= g_type, field, NULL, ATTRIBUTE_STRING) + +/** + * KFUZZTEST_ANNOTATE_ARRAY - annotate a pointer as an array + * + * We define an array as a contiguous memory region containing zero or more + * elements of the same type. + * + * @arg_type: name of the input structure, without the leading "struct ". + * @field: the name of the field to annotate. + */ +#define KFUZZTEST_ANNOTATE_ARRAY(arg_type, field) __KFUZZTEST_ANNOTATE(arg= _type, field, NULL, ATTRIBUTE_ARRAY) + +/** + * KFUZZTEST_ANNOTATE_LEN - annotate a field as the length of another + * + * This expresses the relationship `arg_type.field =3D=3D len(linked_field= )`, where + * `linked_field` is an array. + * + * @arg_type: name of the input structure, without the leading "struct ". + * @field: the name of the field to annotate. + * @linked_field: the name of an array field with length @field. + */ +#define KFUZZTEST_ANNOTATE_LEN(arg_type, field, linked_field) \ + __KFUZZTEST_ANNOTATE(arg_type, field, linked_field, ATTRIBUTE_LEN) + +#define KFUZZTEST_REGIONID_NULL U32_MAX + +/** + * The end of the input should be padded by at least this number of bytes = as + * it is poisoned to detect out of bounds accesses at the end of the last + * region. + */ +#define KFUZZTEST_POISON_SIZE 0x8 + +#endif /* KFUZZTEST_H */ diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index dc0e0c6ed075..49a1748b9f24 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -1947,6 +1947,7 @@ endmenu menu "Kernel Testing and Coverage" =20 source "lib/kunit/Kconfig" +source "lib/kfuzztest/Kconfig" =20 config NOTIFIER_ERROR_INJECTION tristate "Notifier error injection" diff --git a/lib/kfuzztest/Kconfig b/lib/kfuzztest/Kconfig new file mode 100644 index 000000000000..f9fb5abf8d27 --- /dev/null +++ b/lib/kfuzztest/Kconfig @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config KFUZZTEST + bool "KFuzzTest - enable support for internal fuzz targets" + depends on DEBUG_FS && DEBUG_KERNEL + help + Enables support for the kernel fuzz testing framework (KFuzzTest), an + interface for exposing internal kernel functions to a userspace fuzzing + engine. KFuzzTest targets are exposed via a debugfs interface that + accepts serialized userspace inputs, and is designed to make it easier + to fuzz deeply nested kernel code that is hard to reach from the system + call boundary. Using a simple macro-based API, developers can add a new + fuzz target with minimal boilerplate code. + + It is strongly recommended to also enable CONFIG_KASAN for byte-accurate + out-of-bounds detection, as KFuzzTest was designed with this in mind. It + is also recommended to enable CONFIG_KCOV for coverage guided fuzzing. + + WARNING: This exposes internal kernel functions directly to userspace + and must NEVER be enabled in production builds. --=20 2.51.0.384.g4c02a37b29-goog From nobody Thu Oct 2 14:21:31 2025 Received: from mail-wr1-f42.google.com (mail-wr1-f42.google.com [209.85.221.42]) (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 DAA6928CF5E for ; Tue, 16 Sep 2025 09:01:23 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.42 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758013286; cv=none; b=ccYqI6BzxD9yvFANJ7hAbLrZa9F9XiXEWCw7ef/Is8p6Bd0F/+SgnvzOBIjDtPSQorkVh4aUKhV42rLrsM/6+/GBAYtm0qGmPUh+/Oy30S0f9q2H4uBiUb3mwG/gjSaNsZZ6AXMPa8EDLd/qKsTQSYIFEkQ8y7ggyKHujPwnlwM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758013286; c=relaxed/simple; bh=q+j3e9qQ0xdI5ayuaKhLRkZEBF+baMYDLf8IyvIPcVM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=OGrW4BmRZd7Wa8m6DbBlCn/nrqGXIHjBbukraHM0ZabCnfQfleOUTL8Pm/fylRppLmBqBp3ADrCk/+LIxCrC2TEE6zIw3iep1N4z+jPpBhOVsBA+XWSliZsr8ld9VJn5GLVkaAhlj9x4ZUwDGdrj/jjlwc5pSzNcrtQTFKxqQUI= 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=bF8xsKkJ; arc=none smtp.client-ip=209.85.221.42 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="bF8xsKkJ" Received: by mail-wr1-f42.google.com with SMTP id ffacd0b85a97d-3c6abcfd142so2527587f8f.2 for ; Tue, 16 Sep 2025 02:01:23 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1758013282; x=1758618082; 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=wAOqPYvSWVeN9/yE7pImb3Dnjd/YBm0aGHPiEL/y/Dw=; b=bF8xsKkJcOZPO5A3I/eV3UYL0ivMUVStZFiw8TOpkwCwprebOymsmT4SjRNYePOsl/ nlKe8fyhNuomb8XGiZ3oDBqG8I/8cemR3b1ss0nnJEjOABUNHM148ILjQaAaKGMPvhfz 3j/wDGsWFywHc4hTvd+xOU2IZoztS+tPJ2ikbaWJKQP3dKPogfkYSy/OEeV3sAy3JpcC qajXz3lurxhJzo5J5pXKh9A+KBAxZCPg4/Ux91EXS+MfJ9Fmnrt2I/DFsBDs3xrJrkXr QBgMokmteLiTKNtlqtA1uBuCZR13ImjA01cR425JB+dAhRzjOcN2GdXj4MFe4oQ8oXDF woew== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1758013282; x=1758618082; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=wAOqPYvSWVeN9/yE7pImb3Dnjd/YBm0aGHPiEL/y/Dw=; b=oCF5CAHdnZsfbfze5BnoIleIeJ/DuvQH7sqnJnIegcLOaUbD0+wzMvXUqQAavTKSvM TVo6U29zHrTe+Rf3cNmuIUl7EUNZ34iownHGjOJl8kCx9gka+yFCtKhyVdac6AFv7Fyd lzeD6cqAiLQhNtP7c07mnfRUl0SX9jlBwcOtvAD/FQ5LaDK1aybZ8Zbe+Nv8DlMPn6hd bpnSDuw+RiCkefZdRIHVYVee4TiZMHcZWT94UsEmxpicmkmCehNCw9XmVjWgJ3LUKVbb Ey1rHIqauS/MhzbnC6hgIcHdlyLqQ0PYhiVP++qz/3Swwh7BG5Nr4oqA/Cnn1IZ0fhA4 KvoA== X-Forwarded-Encrypted: i=1; AJvYcCWbBybcimMedfDFgOHxo0zHBNXJoBUmCuGb52kzfLPprohXgZVEuY4BKLs2G0rIt1brfAAh4vO25ULMa4U=@vger.kernel.org X-Gm-Message-State: AOJu0YyvufngcBJ0cycHtWmTXrQgnQiMWtx/1D7t/2Lf+JW890LmTY/W /hGSulTMgl5NC4yCkMGYH69ZzBH/pb+3NonQWjwWafieNo8qIDwJiqtf X-Gm-Gg: ASbGncsRMX2TOQosNExXx8BY+IywngMEvKKHhv0BmeGCHbCX4fmW6qfuVOdygFDlsni ufL6ua6r3EIXKBzqMHA/n2zwWgQ3uA0G2rx7en4AAqm193i+mRDqJxRwhp3b4N/G2mebBx3AWQT ZHp8QXvMZA3pPHbAQ/MvVE1SC8baJ16Hemo8zmdMMmTVCBeqEyo47FKJaS0xZJregJTQJLwrFw3 i0lJ+6qEi9kLcsB0JteJZg9gbvT/i1RKYL2OOhVZSClayrik6cKMWFjeNN9g8MY+TSQ8u4PWBl/ R+Axdss6q2n0dnkcwzfuOfFE3pyRjcSmUUp8q5A+n8A7+B4fmq4PZIjqJ1IJOUB5Ux7833a/OB1 2YWqyvzcyVHdhiab1HqhfAwHTYS/vAsnuqA6V1SPnApot6VvmaL6+NGUnKW1oRkcJowuoUhvOTj w06KzG/l529HYW X-Google-Smtp-Source: AGHT+IFaIHSJprd+d+0qq0jjdQAZm5Tx31BeDOIeQBVXABUXuuSsnl2Hqjdm092XL0xM7tasNHavOw== X-Received: by 2002:a5d:5f53:0:b0:3ec:9a32:3642 with SMTP id ffacd0b85a97d-3ec9a323866mr1190593f8f.62.1758013281461; Tue, 16 Sep 2025 02:01:21 -0700 (PDT) Received: from xl-nested.c.googlers.com.com (42.16.79.34.bc.googleusercontent.com. [34.79.16.42]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-45e037186e5sm212975035e9.5.2025.09.16.02.01.20 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 16 Sep 2025 02:01:20 -0700 (PDT) From: Ethan Graham To: ethangraham@google.com, glider@google.com Cc: andreyknvl@gmail.com, andy@kernel.org, brauner@kernel.org, brendan.higgins@linux.dev, davem@davemloft.net, davidgow@google.com, dhowells@redhat.com, dvyukov@google.com, elver@google.com, herbert@gondor.apana.org.au, ignat@cloudflare.com, jack@suse.cz, jannh@google.com, johannes@sipsolutions.net, kasan-dev@googlegroups.com, kees@kernel.org, kunit-dev@googlegroups.com, linux-crypto@vger.kernel.org, linux-kernel@vger.kernel.org, linux-mm@kvack.org, lukas@wunner.de, rmoar@google.com, shuah@kernel.org, tarasmadan@google.com Subject: [PATCH v1 03/10] kfuzztest: implement core module and input processing Date: Tue, 16 Sep 2025 09:01:02 +0000 Message-ID: <20250916090109.91132-4-ethan.w.s.graham@gmail.com> X-Mailer: git-send-email 2.51.0.384.g4c02a37b29-goog In-Reply-To: <20250916090109.91132-1-ethan.w.s.graham@gmail.com> References: <20250916090109.91132-1-ethan.w.s.graham@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" From: Ethan Graham Add the core runtime implementation for KFuzzTest. This includes the module initialization, and the logic for receiving and processing user-provided inputs through debugfs. On module load, the framework discovers all test targets by iterating over the .kfuzztest_target section, creating a corresponding debugfs directory with a write-only 'input' file for each of them. Writing to an 'input' file triggers the main fuzzing sequence: 1. The serialized input is copied from userspace into a kernel buffer. 2. The buffer is parsed to validate the region array and relocation table. 3. Pointers are patched based on the relocation entries, and in KASAN builds the inter-region padding is poisoned. 4. The resulting struct is passed to the user-defined test logic. Signed-off-by: Ethan Graham --- v3: - Update kfuzztest/parse.c interfaces to take `unsigned char *` instead of `void *`, reducing the number of pointer casts. - Expose minimum region alignment via a new debugfs file. - Expose number of successful invocations via a new debugfs file. - Refactor module init function, add _config directory with entries containing KFuzzTest state information. - Account for kasan_poison_range() return value in input parsing logic. - Validate alignment of payload end. - Move static sizeof assertions into /lib/kfuzztest/main.c. - Remove the taint in kfuzztest/main.c. We instead taint the kernel as soon as a fuzz test is invoked for the first time, which is done in the primary FUZZ_TEST macro. v2: - The module's init function now taints the kernel with TAINT_TEST. --- --- include/linux/kfuzztest.h | 4 + lib/Makefile | 2 + lib/kfuzztest/Makefile | 4 + lib/kfuzztest/main.c | 240 ++++++++++++++++++++++++++++++++++++++ lib/kfuzztest/parse.c | 204 ++++++++++++++++++++++++++++++++ 5 files changed, 454 insertions(+) create mode 100644 lib/kfuzztest/Makefile create mode 100644 lib/kfuzztest/main.c create mode 100644 lib/kfuzztest/parse.c diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h index 1e5ed517f291..d90dabba23c4 100644 --- a/include/linux/kfuzztest.h +++ b/include/linux/kfuzztest.h @@ -150,6 +150,9 @@ struct kfuzztest_target { =20 #define KFUZZTEST_MAX_INPUT_SIZE (PAGE_SIZE * 16) =20 +/* Increments a global counter after a successful invocation. */ +void record_invocation(void); + /** * FUZZ_TEST - defines a KFuzzTest target * @@ -243,6 +246,7 @@ struct kfuzztest_target { if (ret < 0) \ goto out; \ kfuzztest_logic_##test_name(arg); \ + record_invocation(); \ ret =3D len; \ out: \ kfree(buffer); \ diff --git a/lib/Makefile b/lib/Makefile index 392ff808c9b9..02789bf88499 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -325,6 +325,8 @@ obj-$(CONFIG_GENERIC_LIB_CMPDI2) +=3D cmpdi2.o obj-$(CONFIG_GENERIC_LIB_UCMPDI2) +=3D ucmpdi2.o obj-$(CONFIG_OBJAGG) +=3D objagg.o =20 +obj-$(CONFIG_KFUZZTEST) +=3D kfuzztest/ + # pldmfw library obj-$(CONFIG_PLDMFW) +=3D pldmfw/ =20 diff --git a/lib/kfuzztest/Makefile b/lib/kfuzztest/Makefile new file mode 100644 index 000000000000..142d16007eea --- /dev/null +++ b/lib/kfuzztest/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_KFUZZTEST) +=3D kfuzztest.o +kfuzztest-objs :=3D main.o parse.o diff --git a/lib/kfuzztest/main.c b/lib/kfuzztest/main.c new file mode 100644 index 000000000000..06f4e3c3c9b2 --- /dev/null +++ b/lib/kfuzztest/main.c @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KFuzzTest core module initialization and debugfs interface. + * + * Copyright 2025 Google LLC + */ +#include +#include +#include +#include +#include +#include +#include + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ethan Graham "); +MODULE_DESCRIPTION("Kernel Fuzz Testing Framework (KFuzzTest)"); + +/* + * Enforce a fixed struct size to ensure a consistent stride when iteratin= g over + * the array of these structs in the dedicated ELF section. + */ +static_assert(sizeof(struct kfuzztest_target) =3D=3D 32, "struct kfuzztest= _target should have size 32"); +static_assert(sizeof(struct kfuzztest_constraint) =3D=3D 64, "struct kfuzz= test_constraint should have size 64"); +static_assert(sizeof(struct kfuzztest_annotation) =3D=3D 32, "struct kfuzz= test_annotation should have size 32"); + +extern const struct kfuzztest_target __kfuzztest_targets_start[]; +extern const struct kfuzztest_target __kfuzztest_targets_end[]; + +/** + * struct kfuzztest_state - global state for the KFuzzTest module + * + * @kfuzztest_dir: The root debugfs directory, /sys/kernel/debug/kfuzztest= /. + * @num_targets: number of registered KFuzzTest targets. + * @target_fops: array of file operations for each registered target. + * @minalign_fops: file operations for the /_config/minalign file. + * @num_invocations_fops: file operations for the /_config/num_invocations= file. + */ +struct kfuzztest_state { + struct dentry *kfuzztest_dir; + atomic_t num_invocations; + size_t num_targets; + + struct file_operations *target_fops; + struct file_operations minalign_fops; + struct file_operations num_invocations_fops; +}; + +static struct kfuzztest_state state; + +void record_invocation(void) +{ + atomic_inc(&state.num_invocations); +} + +static void cleanup_kfuzztest_state(struct kfuzztest_state *st) +{ + debugfs_remove_recursive(st->kfuzztest_dir); + st->num_targets =3D 0; + st->num_invocations =3D (atomic_t)ATOMIC_INIT(0); + kfree(st->target_fops); + st->target_fops =3D NULL; +} + +const umode_t KFUZZTEST_INPUT_PERMS =3D 0222; +const umode_t KFUZZTEST_MINALIGN_PERMS =3D 0444; + +static ssize_t read_cb_integer(struct file *filp, char __user *buf, size_t= count, loff_t *f_pos, size_t value) +{ + char buffer[64]; + int len; + + len =3D scnprintf(buffer, sizeof(buffer), "%zu\n", value); + return simple_read_from_buffer(buf, count, f_pos, buffer, len); +} + +/* + * Callback for /sys/kernel/debug/kfuzztest/_config/minalign. Minalign + * corresponds to the minimum alignment that regions in a KFuzzTest input = must + * satisfy. This callback returns that value in string format. + */ +static ssize_t minalign_read_cb(struct file *filp, char __user *buf, size_= t count, loff_t *f_pos) +{ + int minalign =3D MAX(KFUZZTEST_POISON_SIZE, ARCH_KMALLOC_MINALIGN); + return read_cb_integer(filp, buf, count, f_pos, minalign); +} + +/* + * Callback for /sys/kernel/debug/kfuzztest/_config/num_targets, which ret= urns + * the value in string format. + */ +static ssize_t num_invocations_read_cb(struct file *filp, char __user *buf= , size_t count, loff_t *f_pos) +{ + return read_cb_integer(filp, buf, count, f_pos, atomic_read(&state.num_in= vocations)); +} + +static int create_read_only_file(struct dentry *parent, const char *name, = struct file_operations *fops) +{ + struct dentry *file; + int err =3D 0; + + file =3D debugfs_create_file(name, KFUZZTEST_MINALIGN_PERMS, parent, NULL= , fops); + if (!file) + err =3D -ENOMEM; + else if (IS_ERR(file)) + err =3D PTR_ERR(file); + return err; +} + +static int initialize_config_dir(struct kfuzztest_state *st) +{ + struct dentry *dir; + int err =3D 0; + + dir =3D debugfs_create_dir("_config", st->kfuzztest_dir); + if (!dir) + err =3D -ENOMEM; + else if (IS_ERR(dir)) + err =3D PTR_ERR(dir); + if (err) { + pr_info("kfuzztest: failed to create /_config dir"); + goto out; + } + + st->minalign_fops =3D (struct file_operations){ + .owner =3D THIS_MODULE, + .read =3D minalign_read_cb, + }; + err =3D create_read_only_file(dir, "minalign", &st->minalign_fops); + if (err) { + pr_info("kfuzztest: failed to create /_config/minalign"); + goto out; + } + + st->num_invocations_fops =3D (struct file_operations){ + .owner =3D THIS_MODULE, + .read =3D num_invocations_read_cb, + }; + err =3D create_read_only_file(dir, "num_invocations", &st->num_invocation= s_fops); + if (err) + pr_info("kfuzztest: failed to create /_config/num_invocations"); +out: + return err; +} + +static int initialize_target_dir(struct kfuzztest_state *st, const struct = kfuzztest_target *targ, + struct file_operations *fops) +{ + struct dentry *dir, *input; + int err =3D 0; + + dir =3D debugfs_create_dir(targ->name, st->kfuzztest_dir); + if (!dir) + err =3D -ENOMEM; + else if (IS_ERR(dir)) + err =3D PTR_ERR(dir); + if (err) { + pr_info("kfuzztest: failed to create /kfuzztest/%s dir", targ->name); + goto out; + } + + input =3D debugfs_create_file("input", KFUZZTEST_INPUT_PERMS, dir, NULL, = fops); + if (!input) + err =3D -ENOMEM; + else if (IS_ERR(input)) + err =3D PTR_ERR(input); + if (err) + pr_info("kfuzztest: failed to create /kfuzztest/%s/input", targ->name); +out: + return err; +} + +/** + * kfuzztest_init - initializes the debug filesystem for KFuzzTest + * + * Each registered target in the ".kfuzztest_targets" section gets its own + * subdirectory under "/sys/kernel/debug/kfuzztest/" containing= one + * write-only "input" file used for receiving inputs from userspace. + * Furthermore, a directory "/sys/kernel/debug/kfuzztest/_config" is creat= ed, + * containing two read-only files "minalign" and "num_targets", that return + * the minimum required region alignment and number of targets respectivel= y. + * + * @return 0 on success or an error + */ +static int __init kfuzztest_init(void) +{ + const struct kfuzztest_target *targ; + int err =3D 0; + int i =3D 0; + + state.num_targets =3D __kfuzztest_targets_end - __kfuzztest_targets_start; + state.target_fops =3D kzalloc(sizeof(struct file_operations) * state.num_= targets, GFP_KERNEL); + if (!state.target_fops) + return -ENOMEM; + + /* Create the main "kfuzztest" directory in /sys/kernel/debug. */ + state.kfuzztest_dir =3D debugfs_create_dir("kfuzztest", NULL); + if (!state.kfuzztest_dir) { + pr_warn("kfuzztest: could not create 'kfuzztest' debugfs directory"); + return -ENOMEM; + } + if (IS_ERR(state.kfuzztest_dir)) { + pr_warn("kfuzztest: could not create 'kfuzztest' debugfs directory"); + err =3D PTR_ERR(state.kfuzztest_dir); + state.kfuzztest_dir =3D NULL; + return err; + } + + err =3D initialize_config_dir(&state); + if (err) + goto cleanup_failure; + + for (targ =3D __kfuzztest_targets_start; targ < __kfuzztest_targets_end; = targ++, i++) { + state.target_fops[i] =3D (struct file_operations){ + .owner =3D THIS_MODULE, + .write =3D targ->write_input_cb, + }; + err =3D initialize_target_dir(&state, targ, &state.target_fops[i]); + /* Bail out if a single target fails to initialize. This avoids + * partial setup, and a failure here likely indicates an issue + * with debugfs. */ + if (err) + goto cleanup_failure; + pr_info("kfuzztest: registered target %s", targ->name); + } + return 0; + +cleanup_failure: + cleanup_kfuzztest_state(&state); + return err; +} + +static void __exit kfuzztest_exit(void) +{ + pr_info("kfuzztest: exiting"); + cleanup_kfuzztest_state(&state); +} + +module_init(kfuzztest_init); +module_exit(kfuzztest_exit); diff --git a/lib/kfuzztest/parse.c b/lib/kfuzztest/parse.c new file mode 100644 index 000000000000..5aaeca6a7fde --- /dev/null +++ b/lib/kfuzztest/parse.c @@ -0,0 +1,204 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * KFuzzTest input parsing and validation. + * + * Copyright 2025 Google LLC + */ +#include +#include + +static int kfuzztest_relocate_v0(struct reloc_region_array *regions, struc= t reloc_table *rt, + unsigned char *payload_start, unsigned char *payload_end) +{ + unsigned char *poison_start, *poison_end; + struct reloc_region reg, src, dst; + uintptr_t *ptr_location; + struct reloc_entry re; + size_t i; + int ret; + + /* Patch pointers. */ + for (i =3D 0; i < rt->num_entries; i++) { + re =3D rt->entries[i]; + src =3D regions->regions[re.region_id]; + ptr_location =3D (uintptr_t *)(payload_start + src.offset + re.region_of= fset); + if (re.value =3D=3D KFUZZTEST_REGIONID_NULL) + *ptr_location =3D (uintptr_t)NULL; + else if (re.value < regions->num_regions) { + dst =3D regions->regions[re.value]; + *ptr_location =3D (uintptr_t)(payload_start + dst.offset); + } else { + return -EINVAL; + } + } + + /* Poison the padding between regions. */ + for (i =3D 0; i < regions->num_regions; i++) { + reg =3D regions->regions[i]; + + /* Points to the beginning of the inter-region padding */ + poison_start =3D payload_start + reg.offset + reg.size; + if (i < regions->num_regions - 1) + poison_end =3D payload_start + regions->regions[i + 1].offset; + else + poison_end =3D payload_end; + + if (poison_end > payload_end) + return -EINVAL; + + ret =3D kasan_poison_range(poison_start, poison_end - poison_start); + if (ret) + return ret; + } + + /* Poison the padded area preceding the payload. */ + return kasan_poison_range(payload_start - rt->padding_size, rt->padding_s= ize); +} + +static bool kfuzztest_input_is_valid(struct reloc_region_array *regions, s= truct reloc_table *rt, + unsigned char *payload_start, unsigned char *payload_end) +{ + size_t payload_size =3D payload_end - payload_start; + struct reloc_region reg, next_reg; + size_t usable_payload_size; + uint32_t region_end_offset; + struct reloc_entry reloc; + uint32_t i; + + if (payload_start > payload_end) + return false; + if (payload_size < KFUZZTEST_POISON_SIZE) + return false; + if ((uintptr_t)payload_end % KFUZZTEST_POISON_SIZE) + return false; + usable_payload_size =3D payload_size - KFUZZTEST_POISON_SIZE; + + for (i =3D 0; i < regions->num_regions; i++) { + reg =3D regions->regions[i]; + if (check_add_overflow(reg.offset, reg.size, ®ion_end_offset)) + return false; + if ((size_t)region_end_offset > usable_payload_size) + return false; + + if (i < regions->num_regions - 1) { + next_reg =3D regions->regions[i + 1]; + if (reg.offset > next_reg.offset) + return false; + /* Enforce the minimum poisonable gap between + * consecutive regions. */ + if (reg.offset + reg.size + KFUZZTEST_POISON_SIZE > next_reg.offset) + return false; + } + } + + if (rt->padding_size < KFUZZTEST_POISON_SIZE) { + pr_info("validation failed because rt->padding_size =3D %u", rt->padding= _size); + return false; + } + + for (i =3D 0; i < rt->num_entries; i++) { + reloc =3D rt->entries[i]; + if (reloc.region_id >=3D regions->num_regions) + return false; + if (reloc.value !=3D KFUZZTEST_REGIONID_NULL && reloc.value >=3D regions= ->num_regions) + return false; + + reg =3D regions->regions[reloc.region_id]; + if (reloc.region_offset % (sizeof(uintptr_t)) || reloc.region_offset + s= izeof(uintptr_t) > reg.size) + return false; + } + + return true; +} + +static int kfuzztest_parse_input_v0(unsigned char *input, size_t input_siz= e, struct reloc_region_array **ret_regions, + struct reloc_table **ret_reloc_table, unsigned char **ret_payload_= start, + unsigned char **ret_payload_end) +{ + size_t reloc_entries_size, reloc_regions_size; + unsigned char *payload_end, *payload_start; + size_t reloc_table_size, regions_size; + struct reloc_region_array *regions; + struct reloc_table *rt; + size_t curr_offset =3D 0; + + if (input_size < sizeof(struct reloc_region_array) + sizeof(struct reloc_= table)) + return -EINVAL; + + regions =3D (struct reloc_region_array *)input; + if (check_mul_overflow(regions->num_regions, sizeof(struct reloc_region),= &reloc_regions_size)) + return -EINVAL; + if (check_add_overflow(sizeof(*regions), reloc_regions_size, ®ions_siz= e)) + return -EINVAL; + + curr_offset =3D regions_size; + if (curr_offset > input_size) + return -EINVAL; + if (input_size - curr_offset < sizeof(struct reloc_table)) + return -EINVAL; + + rt =3D (struct reloc_table *)(input + curr_offset); + + if (check_mul_overflow((size_t)rt->num_entries, sizeof(struct reloc_entry= ), &reloc_entries_size)) + return -EINVAL; + if (check_add_overflow(sizeof(*rt), reloc_entries_size, &reloc_table_size= )) + return -EINVAL; + if (check_add_overflow(reloc_table_size, rt->padding_size, &reloc_table_s= ize)) + return -EINVAL; + + if (check_add_overflow(curr_offset, reloc_table_size, &curr_offset)) + return -EINVAL; + if (curr_offset > input_size) + return -EINVAL; + + payload_start =3D input + curr_offset; + payload_end =3D input + input_size; + + if (!kfuzztest_input_is_valid(regions, rt, payload_start, payload_end)) + return -EINVAL; + + *ret_regions =3D regions; + *ret_reloc_table =3D rt; + *ret_payload_start =3D payload_start; + *ret_payload_end =3D payload_end; + return 0; +} + +static int kfuzztest_parse_and_relocate_v0(unsigned char *input, size_t in= put_size, void **arg_ret) +{ + unsigned char *payload_start, *payload_end; + struct reloc_region_array *regions; + struct reloc_table *reloc_table; + int ret; + + ret =3D kfuzztest_parse_input_v0(input, input_size, ®ions, &reloc_tabl= e, &payload_start, &payload_end); + if (ret < 0) + return ret; + + ret =3D kfuzztest_relocate_v0(regions, reloc_table, payload_start, payloa= d_end); + if (ret < 0) + return ret; + *arg_ret =3D (void *)payload_start; + return 0; +} + +int kfuzztest_parse_and_relocate(void *input, size_t input_size, void **ar= g_ret) +{ + size_t header_size =3D 2 * sizeof(u32); + u32 version, magic; + + if (input_size < sizeof(u32) + sizeof(u32)) + return -EINVAL; + + magic =3D *(u32 *)input; + if (magic !=3D KFUZZTEST_HEADER_MAGIC) + return -EINVAL; + + version =3D *(u32 *)(input + sizeof(u32)); + switch (version) { + case KFUZZTEST_V0: + return kfuzztest_parse_and_relocate_v0(input + header_size, input_size -= header_size, arg_ret); + } + + return -EINVAL; +} --=20 2.51.0.384.g4c02a37b29-goog From nobody Thu Oct 2 14:21:31 2025 Received: from mail-wm1-f45.google.com (mail-wm1-f45.google.com [209.85.128.45]) (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 2ED29221FBA for ; Tue, 16 Sep 2025 09:01:25 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.45 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758013289; cv=none; b=kp3XN/DRcN+AHXpyYm5ZCNxB01WjJ0ti0GzBR9s2+wUkR+WQPLRFTp4rWpnUFgyDkwR08dBf77UowL5CrxsmednUizA77xy/YBLmCDf0IVrwmkqti4781OgtMu89rLGssrszJiMFXsQ61JLouXjsKgZyXhbouNWT6tGni1b1efk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758013289; c=relaxed/simple; bh=KhYvHCiFyWzaEiGo63q8YA/gtQQEs0Ru60YAJA0vM+0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ZQxgpR1PcXgdftN35icRZ25UDJTcV9l4Z+TCCRxj11w/xIErOp3+qM9D9uii5Q0jn05+r27TNxvlgZK/r3vEiRLs7KYGhvymNJ6pXw3ryQqqi4dmVvTadBa+VcvvLaY2a7pgjFVykMDpvDZTa/7A4GQIf/OItSwqGBCEcdPs6Zg= 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=hWuHGhJP; arc=none smtp.client-ip=209.85.128.45 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="hWuHGhJP" Received: by mail-wm1-f45.google.com with SMTP id 5b1f17b1804b1-45dec1ae562so47334405e9.1 for ; Tue, 16 Sep 2025 02:01:24 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1758013283; x=1758618083; 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=v722sbbJadlJwwsJi3TM+ZvR5FvVKggc0kwG5mj3X7w=; b=hWuHGhJPZBF9fS5Ld3QIvQzY1oz8tcbviZ3KLroc67poK0uJdayz1SsaJ7wfWbj6Bl YJA/p/qqtVcdS17cDNLTyj8m+LApcMzLQzl6DcVHcFH2iUVE5/yZKC4xKr0SPeAyDDQY tswWlx0cexvTXAkb096ZPS+tRsIum+mMJgi12HJuAg3oPVZFKIkZpz55cRMMDbFZe/Xo pNwC34qYcmwHlAPghiTGNkjawlh5eT5DITQi21S5/zF/GsS8w1QMeKZWWWQjmya+OoH/ NqLV6whjZs4cv0FiZhIVO7ypOovqBwfS3c0Iqp7un6VFohmEIWmKiTxoFQPhrWwK+SmF qDxw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1758013283; x=1758618083; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=v722sbbJadlJwwsJi3TM+ZvR5FvVKggc0kwG5mj3X7w=; b=rYCdrk7b6EdvNpzNp4vtTPC7ZdJqFpNaPA8hy8/k1ttblHTchpMwa1yYWHMt5gFQB3 PUUZ24J7w/0IAPpeJBD8MGTnuw2tVStChsmC74t6Kj+yzwSmOq8p9vMwEw0jEYpI/KqG 5TvDHO5eUNAHTLRHRdYI4qNkFepfEig+RBAu0MWsuBdQw9RdM1unhK6AZc5GX2unOtkc M2gJN5xq+mzweXGN0gPfYq4JbOhcmJ+TQkcyCwzMwmYi9tgQW9NDmPUCnrmiLaYYnt1n N+8giO1qGyll4TnbwOMg78/RZa/Or1LXAe8ThvYXAtM8NlAnwZEQtnv8X3RFNnuXjlWf uCdA== X-Forwarded-Encrypted: i=1; AJvYcCWGd1LG729YnmWHHvIQ2Wuov7H7xdGSpDjn0GYJ+j6jn9Omifd7T+zN9jFm4/bA7G0lCbCrbss+Fb1KVZM=@vger.kernel.org X-Gm-Message-State: AOJu0YwiaKHP9tUFvhRpUSxDXlyQyYGMl1QqRsijMmnwPDRJAOygedqu wc4DYg0bPYSyRCk2FjaDSVLPnxvg9QhpIADZe03eCawN6V/UUJMu7NbT X-Gm-Gg: ASbGnct/pd9+MOYy236/J4IrlOUhGlLE6UMLau3aD2nO1XKosxx5GFkioCg3V5fmlSB WHp+V3lVXonEjAzvxf/E6nq06GfwADeh2LGYYamy3h+dgxWdolAEmWS2qfUeEXMcBvp5hikDxVY A3P6dZTG10LvF38/7bnG9TvvEu+e6Li3zEUzfk1yU5gLNswNjiDtWMJEHPMVZiBNmpnfqZPIU2m JIERFDTixAINjf9xdcgAAmq6/J5JuYNj2LEXyFd51sL4dGhlY23y8zhdAekL/xjQK1k0KKiIEjv hJUkSIO4wIBIc15TBgnySmHALlAqORG80OEhSfk4LGJEQPb6nakH1FaJ7+aVnYOIRFhvPajeNBk wrthtMBrmAe3JeX4cfFwd9ccZ7ZncG3FLtr90ynIzYIyPkMDhe2a33kpqwrUbwQz/YYh9aYK6sa OvE+wA33gYh1mrr/lxND2Peak= X-Google-Smtp-Source: AGHT+IGqfX8hlaQ4zFmLs+irqAWFCdsScOJJQaiIxpjBR3RXMKPTL6A1BFGXQ8xcZNnwvQ5s3rAQ4Q== X-Received: by 2002:a05:600c:247:b0:45b:8ac2:9759 with SMTP id 5b1f17b1804b1-45f211fa054mr114510185e9.23.1758013282611; Tue, 16 Sep 2025 02:01:22 -0700 (PDT) Received: from xl-nested.c.googlers.com.com (42.16.79.34.bc.googleusercontent.com. [34.79.16.42]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-45e037186e5sm212975035e9.5.2025.09.16.02.01.21 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 16 Sep 2025 02:01:21 -0700 (PDT) From: Ethan Graham To: ethangraham@google.com, glider@google.com Cc: andreyknvl@gmail.com, andy@kernel.org, brauner@kernel.org, brendan.higgins@linux.dev, davem@davemloft.net, davidgow@google.com, dhowells@redhat.com, dvyukov@google.com, elver@google.com, herbert@gondor.apana.org.au, ignat@cloudflare.com, jack@suse.cz, jannh@google.com, johannes@sipsolutions.net, kasan-dev@googlegroups.com, kees@kernel.org, kunit-dev@googlegroups.com, linux-crypto@vger.kernel.org, linux-kernel@vger.kernel.org, linux-mm@kvack.org, lukas@wunner.de, rmoar@google.com, shuah@kernel.org, tarasmadan@google.com Subject: [PATCH v1 04/10] tools: add kfuzztest-bridge utility Date: Tue, 16 Sep 2025 09:01:03 +0000 Message-ID: <20250916090109.91132-5-ethan.w.s.graham@gmail.com> X-Mailer: git-send-email 2.51.0.384.g4c02a37b29-goog In-Reply-To: <20250916090109.91132-1-ethan.w.s.graham@gmail.com> References: <20250916090109.91132-1-ethan.w.s.graham@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" From: Ethan Graham Introduce the kfuzztest-bridge tool, a userspace utility for sending structured inputs to KFuzzTest harnesses via debugfs. The bridge takes a textual description of the expected input format, a file containing random bytes, and the name of the target fuzz test. It parses the description, encodes the random data into the binary format expected by the kernel, and writes the result to the corresponding debugfs entry. This allows for both simple manual testing and integration with userspace fuzzing engines. For example, it can be used for smoke testing by providing data from /dev/urandom, or act as a bridge for blob-based fuzzers (e.g., AFL) to target KFuzzTest harnesses. Signed-off-by: Ethan Graham --- v3: - Add additional context in header comment of kfuzztest-bridge/parser.c. - Add some missing NULL checks. - Refactor skip_whitespace() function in input_lexer.c. - Use ctx->minalign to compute correct region alignment, which is read from /sys/kernel/debug/kfuzztest/_config/minalign. --- --- tools/Makefile | 15 +- tools/kfuzztest-bridge/.gitignore | 2 + tools/kfuzztest-bridge/Build | 6 + tools/kfuzztest-bridge/Makefile | 48 ++++ tools/kfuzztest-bridge/bridge.c | 103 +++++++ tools/kfuzztest-bridge/byte_buffer.c | 87 ++++++ tools/kfuzztest-bridge/byte_buffer.h | 31 ++ tools/kfuzztest-bridge/encoder.c | 391 +++++++++++++++++++++++++ tools/kfuzztest-bridge/encoder.h | 16 ++ tools/kfuzztest-bridge/input_lexer.c | 242 ++++++++++++++++ tools/kfuzztest-bridge/input_lexer.h | 57 ++++ tools/kfuzztest-bridge/input_parser.c | 395 ++++++++++++++++++++++++++ tools/kfuzztest-bridge/input_parser.h | 81 ++++++ tools/kfuzztest-bridge/rand_stream.c | 77 +++++ tools/kfuzztest-bridge/rand_stream.h | 57 ++++ 15 files changed, 1602 insertions(+), 6 deletions(-) create mode 100644 tools/kfuzztest-bridge/.gitignore create mode 100644 tools/kfuzztest-bridge/Build create mode 100644 tools/kfuzztest-bridge/Makefile create mode 100644 tools/kfuzztest-bridge/bridge.c create mode 100644 tools/kfuzztest-bridge/byte_buffer.c create mode 100644 tools/kfuzztest-bridge/byte_buffer.h create mode 100644 tools/kfuzztest-bridge/encoder.c create mode 100644 tools/kfuzztest-bridge/encoder.h create mode 100644 tools/kfuzztest-bridge/input_lexer.c create mode 100644 tools/kfuzztest-bridge/input_lexer.h create mode 100644 tools/kfuzztest-bridge/input_parser.c create mode 100644 tools/kfuzztest-bridge/input_parser.h create mode 100644 tools/kfuzztest-bridge/rand_stream.c create mode 100644 tools/kfuzztest-bridge/rand_stream.h diff --git a/tools/Makefile b/tools/Makefile index c31cbbd12c45..7f1dfe022045 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -21,6 +21,7 @@ help: @echo ' hv - tools used when in Hyper-V clients' @echo ' iio - IIO tools' @echo ' intel-speed-select - Intel Speed Select tool' + @echo ' kfuzztest-bridge - KFuzzTest userspace utility' @echo ' kvm_stat - top-like utility for displaying kvm sta= tistics' @echo ' leds - LEDs tools' @echo ' nolibc - nolibc headers testing and installation' @@ -69,7 +70,7 @@ acpi: FORCE cpupower: FORCE $(call descend,power/$@) =20 -counter firewire hv guest bootconfig spi usb virtio mm bpf iio gpio objtoo= l leds wmi firmware debugging tracing: FORCE +counter firewire hv guest bootconfig spi usb virtio mm bpf iio gpio objtoo= l leds wmi firmware debugging tracing kfuzztest-bridge: FORCE $(call descend,$@) =20 bpf/%: FORCE @@ -126,7 +127,8 @@ all: acpi counter cpupower gpio hv firewire \ perf selftests bootconfig spi turbostat usb \ virtio mm bpf x86_energy_perf_policy \ tmon freefall iio objtool kvm_stat wmi \ - debugging tracing thermal thermometer thermal-engine ynl + debugging tracing thermal thermometer thermal-engine ynl \ + kfuzztest-bridge =20 acpi_install: $(call descend,power/$(@:_install=3D),install) @@ -134,7 +136,7 @@ acpi_install: cpupower_install: $(call descend,power/$(@:_install=3D),install) =20 -counter_install firewire_install gpio_install hv_install iio_install perf_= install bootconfig_install spi_install usb_install virtio_install mm_instal= l bpf_install objtool_install wmi_install debugging_install tracing_install: +counter_install firewire_install gpio_install hv_install iio_install perf_= install bootconfig_install spi_install usb_install virtio_install mm_instal= l bpf_install objtool_install wmi_install debugging_install tracing_install= kfuzztest-bridge_install: $(call descend,$(@:_install=3D),install) =20 selftests_install: @@ -170,7 +172,8 @@ install: acpi_install counter_install cpupower_install = gpio_install \ virtio_install mm_install bpf_install x86_energy_perf_policy_install \ tmon_install freefall_install objtool_install kvm_stat_install \ wmi_install debugging_install intel-speed-select_install \ - tracing_install thermometer_install thermal-engine_install ynl_install + tracing_install thermometer_install thermal-engine_install ynl_install \ + kfuzztest-bridge_install =20 acpi_clean: $(call descend,power/acpi,clean) @@ -178,7 +181,7 @@ acpi_clean: cpupower_clean: $(call descend,power/cpupower,clean) =20 -counter_clean hv_clean firewire_clean bootconfig_clean spi_clean usb_clean= virtio_clean mm_clean wmi_clean bpf_clean iio_clean gpio_clean objtool_cle= an leds_clean firmware_clean debugging_clean tracing_clean: +counter_clean hv_clean firewire_clean bootconfig_clean spi_clean usb_clean= virtio_clean mm_clean wmi_clean bpf_clean iio_clean gpio_clean objtool_cle= an leds_clean firmware_clean debugging_clean tracing_clean kfuzztest-bridge= _clean: $(call descend,$(@:_clean=3D),clean) =20 libapi_clean: @@ -230,6 +233,6 @@ clean: acpi_clean counter_clean cpupower_clean hv_clean= firewire_clean \ freefall_clean build_clean libbpf_clean libsubcmd_clean \ gpio_clean objtool_clean leds_clean wmi_clean firmware_clean debugging_c= lean \ intel-speed-select_clean tracing_clean thermal_clean thermometer_clean t= hermal-engine_clean \ - sched_ext_clean ynl_clean + sched_ext_clean ynl_clean kfuzztest-bridge_clean =20 .PHONY: FORCE diff --git a/tools/kfuzztest-bridge/.gitignore b/tools/kfuzztest-bridge/.gi= tignore new file mode 100644 index 000000000000..4aa9fb0d44e2 --- /dev/null +++ b/tools/kfuzztest-bridge/.gitignore @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +kfuzztest-bridge diff --git a/tools/kfuzztest-bridge/Build b/tools/kfuzztest-bridge/Build new file mode 100644 index 000000000000..d07341a226d6 --- /dev/null +++ b/tools/kfuzztest-bridge/Build @@ -0,0 +1,6 @@ +kfuzztest-bridge-y +=3D bridge.o +kfuzztest-bridge-y +=3D byte_buffer.o +kfuzztest-bridge-y +=3D encoder.o +kfuzztest-bridge-y +=3D input_lexer.o +kfuzztest-bridge-y +=3D input_parser.o +kfuzztest-bridge-y +=3D rand_stream.o diff --git a/tools/kfuzztest-bridge/Makefile b/tools/kfuzztest-bridge/Makef= ile new file mode 100644 index 000000000000..3a4437fb0d3f --- /dev/null +++ b/tools/kfuzztest-bridge/Makefile @@ -0,0 +1,48 @@ +# SPDX-License-Identifier: GPL-2.0 +# Makefile for KFuzzTest-Bridge +include ../scripts/Makefile.include + +bindir ?=3D /usr/bin + +ifeq ($(srctree),) +srctree :=3D $(patsubst %/,%,$(dir $(CURDIR))) +srctree :=3D $(patsubst %/,%,$(dir $(srctree))) +endif + +MAKEFLAGS +=3D -r + +override CFLAGS +=3D -O2 -g +override CFLAGS +=3D -Wall -Wextra +override CFLAGS +=3D -D_GNU_SOURCE +override CFLAGS +=3D -I$(OUTPUT)include -I$(srctree)/tools/include + +ALL_TARGETS :=3D kfuzztest-bridge +ALL_PROGRAMS :=3D $(patsubst %,$(OUTPUT)%,$(ALL_TARGETS)) + +KFUZZTEST_BRIDGE_IN :=3D $(OUTPUT)kfuzztest-bridge-in.o +KFUZZTEST_BRIDGE :=3D $(OUTPUT)kfuzztest-bridge + +all: $(ALL_PROGRAMS) + +export srctree OUTPUT CC LD CFLAGS +include $(srctree)/tools/build/Makefile.include + +$(KFUZZTEST_BRIDGE_IN): FORCE + $(Q)$(MAKE) $(build)=3Dkfuzztest-bridge + +$(KFUZZTEST_BRIDGE): $(KFUZZTEST_BRIDGE_IN) + $(QUIET_LINK)$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) + +clean: + rm -f $(ALL_PROGRAMS) + find $(or $(OUTPUT),.) -name '*.o' -delete -o -name '\.*.d' -delete -o -n= ame '\.*.o.cmd' -delete + +install: $(ALL_PROGRAMS) + install -d -m 755 $(DESTDIR)$(bindir); \ + for program in $(ALL_PROGRAMS); do \ + install $$program $(DESTDIR)$(bindir); \ + done + +FORCE: + +.PHONY: all install clean FORCE prepare diff --git a/tools/kfuzztest-bridge/bridge.c b/tools/kfuzztest-bridge/bridg= e.c new file mode 100644 index 000000000000..002e1d2274d7 --- /dev/null +++ b/tools/kfuzztest-bridge/bridge.c @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KFuzzTest tool for sending inputs into a KFuzzTest harness + * + * Copyright 2025 Google LLC + */ +#include +#include +#include +#include +#include + +#include "byte_buffer.h" +#include "encoder.h" +#include "input_lexer.h" +#include "input_parser.h" +#include "rand_stream.h" + +static int invoke_kfuzztest_target(const char *target_name, const char *da= ta, ssize_t data_size) +{ + ssize_t bytes_written; + char *buf =3D NULL; + int ret; + int fd; + + if (asprintf(&buf, "/sys/kernel/debug/kfuzztest/%s/input", target_name) <= 0) + return -ENOMEM; + + fd =3D openat(AT_FDCWD, buf, O_WRONLY, 0); + if (fd < 0) { + ret =3D -errno; + goto out_free; + } + + /* + * A KFuzzTest target's debugfs handler expects the entire input to be + * written in a single contiguous blob. Treat partial writes as errors. + */ + bytes_written =3D write(fd, data, data_size); + if (bytes_written !=3D data_size) { + ret =3D (bytes_written < 0) ? -errno : -EIO; + goto out_close; + } + ret =3D 0; + +out_close: + if (close(fd) !=3D 0 && ret =3D=3D 0) + ret =3D -errno; +out_free: + free(buf); + return ret; +} + +static int invoke_one(const char *input_fmt, const char *fuzz_target, cons= t char *input_filepath) +{ + struct ast_node *ast_prog; + struct byte_buffer *bb; + struct rand_stream *rs; + struct token **tokens; + size_t num_tokens; + size_t num_bytes; + int err; + + err =3D tokenize(input_fmt, &tokens, &num_tokens); + if (err) { + fprintf(stderr, "tokenization failed: %s\n", strerror(-err)); + return err; + } + + err =3D parse(tokens, num_tokens, &ast_prog); + if (err) { + fprintf(stderr, "parsing failed: %s\n", strerror(-err)); + return err; + } + + rs =3D new_rand_stream(input_filepath, 1024); + err =3D encode(ast_prog, rs, &num_bytes, &bb); + if (err =3D=3D STREAM_EOF) { + fprintf(stderr, "encoding failed: reached EOF in %s\n", input_filepath); + return -EINVAL; + } else if (err) { + fprintf(stderr, "encoding failed: %s\n", strerror(-err)); + return err; + } + + err =3D invoke_kfuzztest_target(fuzz_target, bb->buffer, (ssize_t)num_byt= es); + if (err) + fprintf(stderr, "invocation failed: %s\n", strerror(-err)); + destroy_byte_buffer(bb); + destroy_rand_stream(rs); + return err; +} + +int main(int argc, char *argv[]) +{ + if (argc !=3D 4) { + printf("Usage: %s \n"= , argv[0]); + printf("For more detailed information see /Documentation/dev-tools/kfuzz= test.rst\n"); + return 1; + } + + return invoke_one(argv[1], argv[2], argv[3]); +} diff --git a/tools/kfuzztest-bridge/byte_buffer.c b/tools/kfuzztest-bridge/= byte_buffer.c new file mode 100644 index 000000000000..949278bc5257 --- /dev/null +++ b/tools/kfuzztest-bridge/byte_buffer.c @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A simple byte buffer implementation for encoding binary data + * + * Copyright 2025 Google LLC + */ +#include +#include +#include + +#include "byte_buffer.h" + +struct byte_buffer *new_byte_buffer(size_t initial_size) +{ + struct byte_buffer *ret; + size_t alloc_size =3D initial_size >=3D 8 ? initial_size : 8; + + ret =3D malloc(sizeof(*ret)); + if (!ret) + return NULL; + + ret->alloc_size =3D alloc_size; + ret->buffer =3D malloc(alloc_size); + if (!ret->buffer) { + free(ret); + return NULL; + } + ret->num_bytes =3D 0; + return ret; +} + +void destroy_byte_buffer(struct byte_buffer *buf) +{ + free(buf->buffer); + free(buf); +} + +int append_bytes(struct byte_buffer *buf, const char *bytes, size_t num_by= tes) +{ + size_t req_size; + size_t new_size; + char *new_ptr; + + req_size =3D buf->num_bytes + num_bytes; + new_size =3D buf->alloc_size; + + while (req_size > new_size) + new_size *=3D 2; + if (new_size !=3D buf->alloc_size) { + new_ptr =3D realloc(buf->buffer, new_size); + if (!new_ptr) + return -ENOMEM; + buf->buffer =3D new_ptr; + buf->alloc_size =3D new_size; + } + memcpy(buf->buffer + buf->num_bytes, bytes, num_bytes); + buf->num_bytes +=3D num_bytes; + return 0; +} + +int append_byte(struct byte_buffer *buf, char c) +{ + return append_bytes(buf, &c, 1); +} + +int encode_le(struct byte_buffer *buf, uint64_t value, size_t byte_width) +{ + size_t i; + int ret; + + for (i =3D 0; i < byte_width; ++i) { + if ((ret =3D append_byte(buf, (uint8_t)((value >> (i * 8)) & 0xFF)))) { + return ret; + } + } + return 0; +} + +int pad(struct byte_buffer *buf, size_t num_padding) +{ + int ret; + size_t i; + for (i =3D 0; i < num_padding; i++) + if ((ret =3D append_byte(buf, 0))) + return ret; + return 0; +} diff --git a/tools/kfuzztest-bridge/byte_buffer.h b/tools/kfuzztest-bridge/= byte_buffer.h new file mode 100644 index 000000000000..6a31bfb5e78f --- /dev/null +++ b/tools/kfuzztest-bridge/byte_buffer.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A simple byte buffer implementation for encoding binary data + * + * Copyright 2025 Google LLC + */ +#ifndef KFUZZTEST_BRIDGE_BYTE_BUFFER_H +#define KFUZZTEST_BRIDGE_BYTE_BUFFER_H + +#include +#include + +struct byte_buffer { + char *buffer; + size_t num_bytes; + size_t alloc_size; +}; + +struct byte_buffer *new_byte_buffer(size_t initial_size); + +void destroy_byte_buffer(struct byte_buffer *buf); + +int append_bytes(struct byte_buffer *buf, const char *bytes, size_t num_by= tes); + +int append_byte(struct byte_buffer *buf, char c); + +int encode_le(struct byte_buffer *buf, uint64_t value, size_t byte_width); + +int pad(struct byte_buffer *buf, size_t num_padding); + +#endif /* KFUZZTEST_BRIDGE_BYTE_BUFFER_H */ diff --git a/tools/kfuzztest-bridge/encoder.c b/tools/kfuzztest-bridge/enco= der.c new file mode 100644 index 000000000000..dcc964ed51dc --- /dev/null +++ b/tools/kfuzztest-bridge/encoder.c @@ -0,0 +1,391 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Encoder for KFuzzTest binary input format + * + * Copyright 2025 Google LLC + */ +#include +#include +#include +#include +#include + +#include "byte_buffer.h" +#include "input_parser.h" +#include "rand_stream.h" + +#define KFUZZTEST_MAGIC 0xBFACE +#define KFUZZTEST_PROTO_VERSION 0 + +/*=20 + * The KFuzzTest binary input format requires at least 8 bytes of padding + * at the head and tail of every region. + */ +#define KFUZZTEST_POISON_SIZE 8 + +#define BUFSIZE_SMALL 32 +#define BUFSIZE_LARGE 128 + +struct region_info { + const char *name; + uint32_t offset; + uint32_t size; +}; + +struct reloc_info { + uint32_t src_reg; + uint32_t offset; + uint32_t dst_reg; +}; + +struct encoder_ctx { + struct byte_buffer *payload; + struct rand_stream *rand; + + struct region_info *regions; + size_t num_regions; + + struct reloc_info *relocations; + size_t num_relocations; + + size_t minalign; + size_t reg_offset; + int curr_reg; +}; + +static void cleanup_ctx(struct encoder_ctx *ctx) +{ + if (ctx->regions) + free(ctx->regions); + if (ctx->relocations) + free(ctx->relocations); + if (ctx->payload) + destroy_byte_buffer(ctx->payload); +} + +static int read_minalign(struct encoder_ctx *ctx) +{ + const char *minalign_file =3D "/sys/kernel/debug/kfuzztest/_config/minali= gn"; + char buffer[64 + 1]; + int count =3D 0; + int ret =3D 0; + + FILE *f =3D fopen(minalign_file, "r"); + if (!f) + return -ENOENT; + + while (fread(&buffer[count++], 1, 1, f) =3D=3D 1) + ; + buffer[count] =3D '\0'; + + /* + * atoi returns 0 on error. Since we expect a strictly positive + * minalign value on all architectures, a return value of 0 represents + * a failure. + */ + ret =3D atoi(buffer); + if (!ret) { + fclose(f); + return -EINVAL; + } + ctx->minalign =3D atoi(buffer); + fclose(f); + return 0; +} + +static int pad_payload(struct encoder_ctx *ctx, size_t amount) +{ + int ret; + + if ((ret =3D pad(ctx->payload, amount))) + return ret; + ctx->reg_offset +=3D amount; + return 0; +} + +static int align_payload(struct encoder_ctx *ctx, size_t alignment) +{ + size_t pad_amount =3D ROUND_UP_TO_MULTIPLE(ctx->payload->num_bytes, align= ment) - ctx->payload->num_bytes; + return pad_payload(ctx, pad_amount); +} + +static int lookup_reg(struct encoder_ctx *ctx, const char *name) +{ + size_t i; + + for (i =3D 0; i < ctx->num_regions; i++) { + if (strcmp(ctx->regions[i].name, name) =3D=3D 0) + return i; + } + return -ENOENT; +} + +static int add_reloc(struct encoder_ctx *ctx, struct reloc_info reloc) +{ + void *new_ptr =3D realloc(ctx->relocations, (ctx->num_relocations + 1) * = sizeof(struct reloc_info)); + if (!new_ptr) + return -ENOMEM; + + ctx->relocations =3D new_ptr; + ctx->relocations[ctx->num_relocations] =3D reloc; + ctx->num_relocations++; + return 0; +} + +static int build_region_map(struct encoder_ctx *ctx, struct ast_node *top_= level) +{ + struct ast_program *prog; + struct ast_node *reg; + size_t i; + + if (top_level->type !=3D NODE_PROGRAM) + return -EINVAL; + + prog =3D &top_level->data.program; + ctx->regions =3D malloc(prog->num_members * sizeof(struct region_info)); + if (!ctx->regions) + return -ENOMEM; + + ctx->num_regions =3D prog->num_members; + for (i =3D 0; i < ctx->num_regions; i++) { + reg =3D prog->members[i]; + /* Offset is determined after the second pass. */ + ctx->regions[i] =3D (struct region_info){ + .name =3D reg->data.region.name, + .size =3D node_size(reg), + }; + } + return 0; +} +/** + * Encodes a value node as little-endian. A value node is one that has no + * children, and can therefore be directly written into the payload. + */ +static int encode_value_le(struct encoder_ctx *ctx, struct ast_node *node) +{ + size_t array_size; + char rand_char; + size_t length; + size_t i; + int reg; + int ret; + + switch (node->type) { + case NODE_ARRAY: + array_size =3D node->data.array.num_elems * node->data.array.elem_size; + for (i =3D 0; i < array_size; i++) { + if ((ret =3D next_byte(ctx->rand, &rand_char))) + return ret; + if ((ret =3D append_byte(ctx->payload, rand_char))) + return ret; + } + ctx->reg_offset +=3D array_size; + if (node->data.array.null_terminated) { + if ((ret =3D pad_payload(ctx, 1))) + return ret; + ctx->reg_offset++; + } + break; + case NODE_LENGTH: + reg =3D lookup_reg(ctx, node->data.length.length_of); + if (reg < 0) + return reg; + length =3D ctx->regions[reg].size; + if ((ret =3D encode_le(ctx->payload, length, node->data.length.byte_widt= h))) + return ret; + ctx->reg_offset +=3D node->data.length.byte_width; + break; + case NODE_PRIMITIVE: + for (i =3D 0; i < node->data.primitive.byte_width; i++) { + if ((ret =3D next_byte(ctx->rand, &rand_char))) + return ret; + if ((ret =3D append_byte(ctx->payload, rand_char))) + return ret; + } + ctx->reg_offset +=3D node->data.primitive.byte_width; + break; + case NODE_POINTER: + reg =3D lookup_reg(ctx, node->data.pointer.points_to); + if (reg < 0) + return reg; + if ((ret =3D add_reloc(ctx, (struct reloc_info){ .src_reg =3D ctx->curr_= reg, + .offset =3D ctx->reg_offset, + .dst_reg =3D reg }))) + return ret; + /* Placeholder pointer value, as pointers are patched by KFuzzTest anywa= ys. */ + if ((ret =3D encode_le(ctx->payload, UINTPTR_MAX, sizeof(uintptr_t)))) + return ret; + ctx->reg_offset +=3D sizeof(uintptr_t); + break; + case NODE_PROGRAM: + case NODE_REGION: + default: + return -EINVAL; + } + return 0; +} + +static int encode_region(struct encoder_ctx *ctx, struct ast_region *reg) +{ + struct ast_node *child; + size_t i; + int ret; + + ctx->reg_offset =3D 0; + for (i =3D 0; i < reg->num_members; i++) { + child =3D reg->members[i]; + if ((ret =3D align_payload(ctx, node_alignment(child)))) + return ret; + if ((ret =3D encode_value_le(ctx, child))) + return ret; + } + return 0; +} + +static int encode_payload(struct encoder_ctx *ctx, struct ast_node *top_le= vel) +{ + struct ast_node *reg; + size_t i; + int ret; + + for (i =3D 0; i < ctx->num_regions; i++) { + reg =3D top_level->data.program.members[i]; + if ((ret =3D align_payload(ctx, MAX(ctx->minalign, node_alignment(reg)))= )) + return ret; + + ctx->curr_reg =3D i; + ctx->regions[i].offset =3D ctx->payload->num_bytes; + if ((ret =3D encode_region(ctx, ®->data.region))) + return ret; + if ((ret =3D pad_payload(ctx, KFUZZTEST_POISON_SIZE))) + return ret; + } + return align_payload(ctx, ctx->minalign); +} + +static int encode_region_array(struct encoder_ctx *ctx, struct byte_buffer= **ret) +{ + struct byte_buffer *reg_array; + struct region_info info; + int retcode; + size_t i; + + reg_array =3D new_byte_buffer(BUFSIZE_SMALL); + if (!reg_array) + return -ENOMEM; + + if ((retcode =3D encode_le(reg_array, ctx->num_regions, sizeof(uint32_t))= )) + goto fail; + + for (i =3D 0; i < ctx->num_regions; i++) { + info =3D ctx->regions[i]; + if ((retcode =3D encode_le(reg_array, info.offset, sizeof(uint32_t)))) + goto fail; + if ((retcode =3D encode_le(reg_array, info.size, sizeof(uint32_t)))) + goto fail; + } + *ret =3D reg_array; + return 0; + +fail: + destroy_byte_buffer(reg_array); + return retcode; +} + +static int encode_reloc_table(struct encoder_ctx *ctx, size_t padding_amou= nt, struct byte_buffer **ret) +{ + struct byte_buffer *reloc_table; + struct reloc_info info; + int retcode; + size_t i; + + reloc_table =3D new_byte_buffer(BUFSIZE_SMALL); + if (!reloc_table) + return -ENOMEM; + + if ((retcode =3D encode_le(reloc_table, ctx->num_relocations, sizeof(uint= 32_t))) || + (retcode =3D encode_le(reloc_table, padding_amount, sizeof(uint32_t))= )) + goto fail; + + for (i =3D 0; i < ctx->num_relocations; i++) { + info =3D ctx->relocations[i]; + if ((retcode =3D encode_le(reloc_table, info.src_reg, sizeof(uint32_t)))= || + (retcode =3D encode_le(reloc_table, info.offset, sizeof(uint32_t))) = || + (retcode =3D encode_le(reloc_table, info.dst_reg, sizeof(uint32_t)))) + goto fail; + } + pad(reloc_table, padding_amount); + *ret =3D reloc_table; + return 0; + +fail: + destroy_byte_buffer(reloc_table); + return retcode; +} + +static size_t reloc_table_size(struct encoder_ctx *ctx) +{ + return 2 * sizeof(uint32_t) + 3 * ctx->num_relocations * sizeof(uint32_t); +} + +int encode(struct ast_node *top_level, struct rand_stream *r, size_t *num_= bytes, struct byte_buffer **ret) +{ + struct byte_buffer *region_array =3D NULL; + struct byte_buffer *final_buffer =3D NULL; + struct byte_buffer *reloc_table =3D NULL; + size_t header_size; + int alignment; + int retcode; + + struct encoder_ctx ctx =3D { 0 }; + if ((retcode =3D read_minalign(&ctx))) + return retcode; + + if ((retcode =3D build_region_map(&ctx, top_level))) + goto fail; + + ctx.rand =3D r; + ctx.payload =3D new_byte_buffer(BUFSIZE_SMALL); + if (!ctx.payload) { + retcode =3D -ENOMEM; + goto fail; + } + if ((retcode =3D encode_payload(&ctx, top_level))) + goto fail; + + if ((retcode =3D encode_region_array(&ctx, ®ion_array))) + goto fail; + + header_size =3D sizeof(uint64_t) + region_array->num_bytes + reloc_table_= size(&ctx); + alignment =3D node_alignment(top_level); + if ((retcode =3D encode_reloc_table( + &ctx, ROUND_UP_TO_MULTIPLE(header_size + KFUZZTEST_POISON_SIZE, ali= gnment) - header_size, + &reloc_table))) + goto fail; + + final_buffer =3D new_byte_buffer(BUFSIZE_LARGE); + if (!final_buffer) { + retcode =3D -ENOMEM; + goto fail; + } + + if ((retcode =3D encode_le(final_buffer, KFUZZTEST_MAGIC, sizeof(uint32_t= ))) || + (retcode =3D encode_le(final_buffer, KFUZZTEST_PROTO_VERSION, sizeof(= uint32_t))) || + (retcode =3D append_bytes(final_buffer, region_array->buffer, region_= array->num_bytes)) || + (retcode =3D append_bytes(final_buffer, reloc_table->buffer, reloc_ta= ble->num_bytes)) || + (retcode =3D append_bytes(final_buffer, ctx.payload->buffer, ctx.payl= oad->num_bytes))) { + destroy_byte_buffer(final_buffer); + goto fail; + } + + *num_bytes =3D final_buffer->num_bytes; + *ret =3D final_buffer; + +fail: + if (region_array) + destroy_byte_buffer(region_array); + if (reloc_table) + destroy_byte_buffer(reloc_table); + cleanup_ctx(&ctx); + return retcode; +} diff --git a/tools/kfuzztest-bridge/encoder.h b/tools/kfuzztest-bridge/enco= der.h new file mode 100644 index 000000000000..73f8c4b7893c --- /dev/null +++ b/tools/kfuzztest-bridge/encoder.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Encoder for KFuzzTest binary input format + * + * Copyright 2025 Google LLC + */ +#ifndef KFUZZTEST_BRIDGE_ENCODER_H +#define KFUZZTEST_BRIDGE_ENCODER_H + +#include "input_parser.h" +#include "rand_stream.h" +#include "byte_buffer.h" + +int encode(struct ast_node *top_level, struct rand_stream *r, size_t *num_= bytes, struct byte_buffer **ret); + +#endif /* KFUZZTEST_BRIDGE_ENCODER_H */ diff --git a/tools/kfuzztest-bridge/input_lexer.c b/tools/kfuzztest-bridge/= input_lexer.c new file mode 100644 index 000000000000..56f763e4394f --- /dev/null +++ b/tools/kfuzztest-bridge/input_lexer.c @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Parser for KFuzzTest textual input format + * + * Copyright 2025 Google LLC + */ +#include +#include +#include +#include +#include +#include + +#include "input_lexer.h" + +struct keyword_map { + const char *keyword; + enum token_type type; +}; + +static struct keyword_map keywords[] =3D { + { "ptr", TOKEN_KEYWORD_PTR }, { "arr", TOKEN_KEYWORD_ARR }, { "len", TOKE= N_KEYWORD_LEN }, + { "str", TOKEN_KEYWORD_STR }, { "u8", TOKEN_KEYWORD_U8 }, { "u16", TOKE= N_KEYWORD_U16 }, + { "u32", TOKEN_KEYWORD_U32 }, { "u64", TOKEN_KEYWORD_U64 }, +}; + +static struct token *make_token(enum token_type type, size_t position) +{ + struct token *ret =3D calloc(1, sizeof(*ret)); + ret->position =3D position; + ret->type =3D type; + return ret; +} + +struct lexer { + const char *start; + const char *current; + size_t position; +}; + +static char advance(struct lexer *l) +{ + l->current++; + l->position++; + return l->current[-1]; +} + +static void retreat(struct lexer *l) +{ + l->position--; + l->current--; +} + +static char peek(struct lexer *l) +{ + return *l->current; +} + +static bool is_digit(char c) +{ + return c >=3D '0' && c <=3D '9'; +} + +static bool is_alpha(char c) +{ + return (c >=3D 'a' && c <=3D 'z') || (c >=3D 'A' && c <=3D 'Z'); +} + +static bool is_whitespace(char c) +{ + switch (c) { + case ' ': + case '\r': + case '\t': + case '\n': + return true; + default: + return false; + } +} + +static void skip_whitespace(struct lexer *l) +{ + while (is_whitespace(peek(l))) + advance(l); +} + +static struct token *number(struct lexer *l) +{ + struct token *tok; + uint64_t value; + while (is_digit(peek(l))) + advance(l); + value =3D strtoull(l->start, NULL, 10); + tok =3D make_token(TOKEN_INTEGER, l->position); + tok->data.integer =3D value; + return tok; +} + +static enum token_type check_keyword(struct lexer *l, const char *keyword,= enum token_type type) +{ + size_t len =3D strlen(keyword); + + if (((size_t)(l->current - l->start) =3D=3D len) && strncmp(l->start, key= word, len) =3D=3D 0) + return type; + return TOKEN_IDENTIFIER; +} + +static struct token *identifier(struct lexer *l) +{ + enum token_type type =3D TOKEN_IDENTIFIER; + struct token *tok; + size_t i; + + while (is_digit(peek(l)) || is_alpha(peek(l)) || peek(l) =3D=3D '_') + advance(l); + + for (i =3D 0; i < ARRAY_SIZE(keywords); i++) { + if (check_keyword(l, keywords[i].keyword, keywords[i].type) !=3D TOKEN_I= DENTIFIER) { + type =3D keywords[i].type; + break; + } + } + + tok =3D make_token(type, l->position); + if (!tok) + return NULL; + if (type =3D=3D TOKEN_IDENTIFIER) { + tok->data.identifier.start =3D l->start; + tok->data.identifier.length =3D l->current - l->start; + } + return tok; +} + +static struct token *scan_token(struct lexer *l) +{ + char c; + skip_whitespace(l); + + l->start =3D l->current; + c =3D peek(l); + + if (c =3D=3D '\0') + return make_token(TOKEN_EOF, l->position); + + advance(l); + switch (c) { + case '{': + return make_token(TOKEN_LBRACE, l->position); + case '}': + return make_token(TOKEN_RBRACE, l->position); + case '[': + return make_token(TOKEN_LBRACKET, l->position); + case ']': + return make_token(TOKEN_RBRACKET, l->position); + case ',': + return make_token(TOKEN_COMMA, l->position); + case ';': + return make_token(TOKEN_SEMICOLON, l->position); + default: + retreat(l); + if (is_digit(c)) + return number(l); + if (is_alpha(c) || c =3D=3D '_') + return identifier(l); + return make_token(TOKEN_ERROR, l->position); + } +} + +int primitive_byte_width(enum token_type type) +{ + switch (type) { + case TOKEN_KEYWORD_U8: + return 1; + case TOKEN_KEYWORD_U16: + return 2; + case TOKEN_KEYWORD_U32: + return 4; + case TOKEN_KEYWORD_U64: + return 8; + default: + return 0; + } +} + +int tokenize(const char *input, struct token ***tokens, size_t *num_tokens) +{ + struct lexer l =3D { .start =3D input, .current =3D input }; + struct token **ret_tokens; + size_t token_arr_size; + size_t token_count; + struct token *tok; + void *tmp; + size_t i; + int err; + + token_arr_size =3D 128; + ret_tokens =3D calloc(token_arr_size, sizeof(struct token *)); + if (!ret_tokens) + return -ENOMEM; + + token_count =3D 0; + do { + tok =3D scan_token(&l); + if (!tok) { + err =3D -ENOMEM; + goto failure; + } + + if (token_count =3D=3D token_arr_size) { + token_arr_size *=3D 2; + tmp =3D realloc(ret_tokens, token_arr_size); + if (!tmp) { + err =3D -ENOMEM; + goto failure; + } + ret_tokens =3D tmp; + } + + ret_tokens[token_count] =3D tok; + if (tok->type =3D=3D TOKEN_ERROR) { + err =3D -EINVAL; + goto failure; + } + token_count++; + } while (tok->type !=3D TOKEN_EOF); + + *tokens =3D ret_tokens; + *num_tokens =3D token_count; + return 0; + +failure: + for (i =3D 0; i < token_count; i++) + free(ret_tokens[i]); + free(ret_tokens); + return err; +} + +bool is_primitive(struct token *tok) +{ + return tok->type >=3D TOKEN_KEYWORD_U8 && tok->type <=3D TOKEN_KEYWORD_U6= 4; +} diff --git a/tools/kfuzztest-bridge/input_lexer.h b/tools/kfuzztest-bridge/= input_lexer.h new file mode 100644 index 000000000000..bdc55e08a3eb --- /dev/null +++ b/tools/kfuzztest-bridge/input_lexer.h @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Lexer for KFuzzTest textual input format + * + * Copyright 2025 Google LLC + */ +#ifndef KFUZZTEST_BRIDGE_INPUT_LEXER_H +#define KFUZZTEST_BRIDGE_INPUT_LEXER_H + +#include +#include +#include + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) + +enum token_type { + TOKEN_LBRACE, + TOKEN_RBRACE, + TOKEN_LBRACKET, + TOKEN_RBRACKET, + TOKEN_COMMA, + TOKEN_SEMICOLON, + + TOKEN_KEYWORD_PTR, + TOKEN_KEYWORD_ARR, + TOKEN_KEYWORD_LEN, + TOKEN_KEYWORD_STR, + TOKEN_KEYWORD_U8, + TOKEN_KEYWORD_U16, + TOKEN_KEYWORD_U32, + TOKEN_KEYWORD_U64, + + TOKEN_IDENTIFIER, + TOKEN_INTEGER, + + TOKEN_EOF, + TOKEN_ERROR, +}; + +struct token { + enum token_type type; + union { + uint64_t integer; + struct { + const char *start; + size_t length; + } identifier; + } data; + int position; +}; + +int tokenize(const char *input, struct token ***tokens, size_t *num_tokens= ); + +bool is_primitive(struct token *tok); +int primitive_byte_width(enum token_type type); + +#endif /* KFUZZTEST_BRIDGE_INPUT_LEXER_H */ diff --git a/tools/kfuzztest-bridge/input_parser.c b/tools/kfuzztest-bridge= /input_parser.c new file mode 100644 index 000000000000..61d324b9dc0e --- /dev/null +++ b/tools/kfuzztest-bridge/input_parser.c @@ -0,0 +1,395 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Parser for the KFuzzTest textual input format + * + * This file implements a parser for a simple DSL used to describe C-like = data + * structures. This format allows the kfuzztest-bridge tool to encode a ra= ndom + * byte stream into the structured binary format expected by a KFuzzTest + * harness. + * + * The format consists of semicolon-separated "regions," which are analogo= us to + * C structs. For example: + * + * "my_struct { ptr[buf] len[buf, u64] }; buf { arr[u8, 42] };" + * + * This describes a `my_struct` region that contains a pointer to a `buf` = region + * and its corresponding length encoded over 8 bytes, where `buf` itself + * contains a 42-byte array. + * + * Copyright 2025 Google LLC + */ +#include +#include +#include + +#include "input_lexer.h" +#include "input_parser.h" + + +static struct token *peek(struct parser *p) +{ + return p->tokens[p->curr_token]; +} + +static struct token *advance(struct parser *p) +{ + struct token *tok; + if (p->curr_token >=3D p->token_count) + return NULL; + tok =3D peek(p); + p->curr_token++; + return tok; +} + +static struct token *consume(struct parser *p, enum token_type type, const= char *err_msg) +{ + if (peek(p)->type !=3D type) { + printf("parser failure at position %d: %s\n", peek(p)->position, err_msg= ); + return NULL; + } + return advance(p); +} + +static bool match(struct parser *p, enum token_type t) +{ + struct token *tok =3D peek(p); + return tok->type =3D=3D t; +} + +static int parse_primitive(struct parser *p, struct ast_node **node_ret) +{ + struct ast_node *ret; + struct token *tok; + int byte_width; + + tok =3D advance(p); + byte_width =3D primitive_byte_width(tok->type); + if (!byte_width) + return -EINVAL; + + ret =3D malloc(sizeof(*ret)); + if (!ret) + return -ENOMEM; + + ret->type =3D NODE_PRIMITIVE; + ret->data.primitive.byte_width =3D byte_width; + *node_ret =3D ret; + return 0; +} + +static int parse_ptr(struct parser *p, struct ast_node **node_ret) +{ + const char *points_to; + struct ast_node *ret; + struct token *tok; + if (!consume(p, TOKEN_KEYWORD_PTR, "expected 'ptr'")) + return -EINVAL; + if (!consume(p, TOKEN_LBRACKET, "expected '['")) + return -EINVAL; + + tok =3D consume(p, TOKEN_IDENTIFIER, "expected identifier"); + if (!tok) + return -EINVAL; + + if (!consume(p, TOKEN_RBRACKET, "expected ']'")) + return -EINVAL; + + ret =3D malloc(sizeof(*ret)); + ret->type =3D NODE_POINTER; + + points_to =3D strndup(tok->data.identifier.start, tok->data.identifier.le= ngth); + if (!points_to) { + free(ret); + return -EINVAL; + } + + ret->data.pointer.points_to =3D points_to; + *node_ret =3D ret; + return 0; +} + +static int parse_arr(struct parser *p, struct ast_node **node_ret) +{ + struct token *type, *num_elems; + struct ast_node *ret; + + if (!consume(p, TOKEN_KEYWORD_ARR, "expected 'arr'") || !consume(p, TOKEN= _LBRACKET, "expected '['")) + return -EINVAL; + + type =3D advance(p); + if (!is_primitive(type)) + return -EINVAL; + + if (!consume(p, TOKEN_COMMA, "expected ','")) + return -EINVAL; + + num_elems =3D consume(p, TOKEN_INTEGER, "expected integer"); + if (!num_elems) + return -EINVAL; + + if (!consume(p, TOKEN_RBRACKET, "expected ']'")) + return -EINVAL; + + ret =3D malloc(sizeof(*ret)); + if (!ret) + return -ENOMEM; + + ret->type =3D NODE_ARRAY; + ret->data.array.num_elems =3D num_elems->data.integer; + ret->data.array.elem_size =3D primitive_byte_width(type->type); + ret->data.array.null_terminated =3D false; + *node_ret =3D ret; + return 0; +} + +static int parse_str(struct parser *p, struct ast_node **node_ret) +{ + struct ast_node *ret; + struct token *len; + + if (!consume(p, TOKEN_KEYWORD_STR, "expected 'str'") || !consume(p, TOKEN= _LBRACKET, "expected '['")) + return -EINVAL; + + len =3D consume(p, TOKEN_INTEGER, "expected integer"); + if (!len) + return -EINVAL; + + if (!consume(p, TOKEN_RBRACKET, "expected ']'")) + return -EINVAL; + + ret =3D malloc(sizeof(*ret)); + if (!ret) + return -ENOMEM; + + /* A string is the susbet of byte arrays that are null-terminated. */ + ret->type =3D NODE_ARRAY; + ret->data.array.num_elems =3D len->data.integer; + ret->data.array.elem_size =3D sizeof(char); + ret->data.array.null_terminated =3D true; + *node_ret =3D ret; + return 0; +} + +static int parse_len(struct parser *p, struct ast_node **node_ret) +{ + struct token *type, *len; + struct ast_node *ret; + + if (!consume(p, TOKEN_KEYWORD_LEN, "expected 'len'") || !consume(p, TOKEN= _LBRACKET, "expected '['")) + return -EINVAL; + + len =3D advance(p); + if (len->type !=3D TOKEN_IDENTIFIER) + return -EINVAL; + + if (!consume(p, TOKEN_COMMA, "expected ','")) + return -EINVAL; + + type =3D advance(p); + if (!is_primitive(type)) + return -EINVAL; + + if (!consume(p, TOKEN_RBRACKET, "expected ']'")) + return -EINVAL; + + ret =3D malloc(sizeof(*ret)); + if (!ret) + return -ENOMEM; + ret->type =3D NODE_LENGTH; + ret->data.length.length_of =3D strndup(len->data.identifier.start, len->d= ata.identifier.length); + ret->data.length.byte_width =3D primitive_byte_width(type->type); + + *node_ret =3D ret; + return 0; +} + +static int parse_type(struct parser *p, struct ast_node **node_ret) +{ + if (is_primitive(peek(p))) + return parse_primitive(p, node_ret); + + if (peek(p)->type =3D=3D TOKEN_KEYWORD_PTR) + return parse_ptr(p, node_ret); + + if (peek(p)->type =3D=3D TOKEN_KEYWORD_ARR) + return parse_arr(p, node_ret); + + if (peek(p)->type =3D=3D TOKEN_KEYWORD_STR) + return parse_str(p, node_ret); + + if (peek(p)->type =3D=3D TOKEN_KEYWORD_LEN) + return parse_len(p, node_ret); + + return -EINVAL; +} + +static int parse_region(struct parser *p, struct ast_node **node_ret) +{ + struct token *tok, *identifier; + struct ast_region *region; + struct ast_node *node; + struct ast_node *ret; + void *new_ptr; + size_t i; + int err; + + identifier =3D consume(p, TOKEN_IDENTIFIER, "expected identifier"); + if (!identifier) + return -EINVAL; + + ret =3D malloc(sizeof(*ret)); + if (!ret) + return -ENOMEM; + + tok =3D consume(p, TOKEN_LBRACE, "expected '{'"); + if (!tok) { + err =3D -EINVAL; + goto fail_early; + } + + region =3D &ret->data.region; + region->name =3D strndup(identifier->data.identifier.start, identifier->d= ata.identifier.length); + if (!region->name) { + err =3D -ENOMEM; + goto fail_early; + } + + region->num_members =3D 0; + while (!match(p, TOKEN_RBRACE)) { + err =3D parse_type(p, &node); + if (err) + goto fail; + new_ptr =3D realloc(region->members, ++region->num_members * sizeof(stru= ct ast_node *)); + if (!new_ptr) { + err =3D -ENOMEM; + goto fail; + } + region->members =3D new_ptr; + region->members[region->num_members - 1] =3D node; + } + + if (!consume(p, TOKEN_RBRACE, "expected '}'") || !consume(p, TOKEN_SEMICO= LON, "expected ';'")) { + err =3D -EINVAL; + goto fail; + } + + ret->type =3D NODE_REGION; + *node_ret =3D ret; + return 0; + +fail: + for (i =3D 0; i < region->num_members; i++) + free(region->members[i]); + free((void *)region->name); + free(region->members); +fail_early: + free(ret); + return err; +} + +static int parse_program(struct parser *p, struct ast_node **node_ret) +{ + struct ast_program *prog; + struct ast_node *reg; + struct ast_node *ret; + void *new_ptr; + size_t i; + int err; + + ret =3D malloc(sizeof(*ret)); + if (!ret) + return -ENOMEM; + ret->type =3D NODE_PROGRAM; + + prog =3D &ret->data.program; + prog->num_members =3D 0; + prog->members =3D NULL; + while (!match(p, TOKEN_EOF)) { + err =3D parse_region(p, ®); + if (err) + goto fail; + + new_ptr =3D realloc(prog->members, ++prog->num_members * sizeof(struct a= st_node *)); + if (!new_ptr) { + err =3D -ENOMEM; + goto fail; + } + prog->members =3D new_ptr; + prog->members[prog->num_members - 1] =3D reg; + } + + *node_ret =3D ret; + return 0; + +fail: + for (i =3D 0; i < prog->num_members; i++) + free(prog->members[i]); + free(prog->members); + free(ret); + return err; +} + +size_t node_alignment(struct ast_node *node) +{ + size_t max_alignment =3D 1; + size_t i; + + switch (node->type) { + case NODE_PROGRAM: + for (i =3D 0; i < node->data.program.num_members; i++) + max_alignment =3D MAX(max_alignment, node_alignment(node->data.program.= members[i])); + return max_alignment; + case NODE_REGION: + for (i =3D 0; i < node->data.region.num_members; i++) + max_alignment =3D MAX(max_alignment, node_alignment(node->data.region.m= embers[i])); + return max_alignment; + case NODE_ARRAY: + return node->data.array.elem_size; + case NODE_LENGTH: + return node->data.length.byte_width; + case NODE_PRIMITIVE: + /* Primitives are aligned to their size. */ + return node->data.primitive.byte_width; + case NODE_POINTER: + return sizeof(uintptr_t); + } + + /* Anything should be at least 1-byte-aligned. */ + return 1; +} + +size_t node_size(struct ast_node *node) +{ + size_t total =3D 0; + size_t i; + + switch (node->type) { + case NODE_PROGRAM: + for (i =3D 0; i < node->data.program.num_members; i++) + total +=3D node_size(node->data.program.members[i]); + return total; + case NODE_REGION: + for (i =3D 0; i < node->data.region.num_members; i++) { + /* Account for padding within region. */ + total =3D ROUND_UP_TO_MULTIPLE(total, node_alignment(node->data.region.= members[i])); + total +=3D node_size(node->data.region.members[i]); + } + return total; + case NODE_ARRAY: + return node->data.array.elem_size * node->data.array.num_elems + + (node->data.array.null_terminated ? 1 : 0); + case NODE_LENGTH: + return node->data.length.byte_width; + case NODE_PRIMITIVE: + return node->data.primitive.byte_width; + case NODE_POINTER: + return sizeof(uintptr_t); + } + return 0; +} + +int parse(struct token **tokens, size_t token_count, struct ast_node **nod= e_ret) +{ + struct parser p =3D { .tokens =3D tokens, .token_count =3D token_count, .= curr_token =3D 0 }; + return parse_program(&p, node_ret); +} diff --git a/tools/kfuzztest-bridge/input_parser.h b/tools/kfuzztest-bridge= /input_parser.h new file mode 100644 index 000000000000..7e965fd5def5 --- /dev/null +++ b/tools/kfuzztest-bridge/input_parser.h @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Parser for KFuzzTest textual input format + * + * Copyright 2025 Google LLC + */ +#ifndef KFUZZTEST_BRIDGE_INPUT_PARSER_H +#define KFUZZTEST_BRIDGE_INPUT_PARSER_H + +#include + +/* Rounds x up to the nearest multiple of n. */ +#define ROUND_UP_TO_MULTIPLE(x, n) (((n) =3D=3D 0) ? (0) : (((x) + (n) - 1= ) / (n)) * (n)) + +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + +enum ast_node_type { + NODE_PROGRAM, + NODE_REGION, + NODE_ARRAY, + NODE_LENGTH, + NODE_PRIMITIVE, + NODE_POINTER, +}; + +struct ast_node; /* Forward declaration. */ + +struct ast_program { + struct ast_node **members; + size_t num_members; +}; + +struct ast_region { + const char *name; + struct ast_node **members; + size_t num_members; +}; + +struct ast_array { + int elem_size; + int null_terminated; /* True iff the array should always end with 0. */ + size_t num_elems; +}; + +struct ast_length { + size_t byte_width; + const char *length_of; +}; + +struct ast_primitive { + size_t byte_width; +}; + +struct ast_pointer { + const char *points_to; +}; + +struct ast_node { + enum ast_node_type type; + union { + struct ast_program program; + struct ast_region region; + struct ast_array array; + struct ast_length length; + struct ast_primitive primitive; + struct ast_pointer pointer; + } data; +}; + +struct parser { + struct token **tokens; + size_t token_count; + size_t curr_token; +}; + +int parse(struct token **tokens, size_t token_count, struct ast_node **nod= e_ret); + +size_t node_size(struct ast_node *node); +size_t node_alignment(struct ast_node *node); + +#endif /* KFUZZTEST_BRIDGE_INPUT_PARSER_H */ diff --git a/tools/kfuzztest-bridge/rand_stream.c b/tools/kfuzztest-bridge/= rand_stream.c new file mode 100644 index 000000000000..bca6b3de5aad --- /dev/null +++ b/tools/kfuzztest-bridge/rand_stream.c @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Implements a cached file-reader for iterating over a byte stream of + * pseudo-random data + * + * Copyright 2025 Google LLC + */ +#include "rand_stream.h" + +static int refill(struct rand_stream *rs) +{ + rs->valid_bytes =3D fread(rs->buffer, sizeof(char), rs->buffer_size, rs->= source); + rs->buffer_pos =3D 0; + if (rs->valid_bytes !=3D rs->buffer_size && ferror(rs->source)) + return ferror(rs->source); + return 0; +} + +struct rand_stream *new_rand_stream(const char *path_to_file, size_t cache= _size) +{ + struct rand_stream *rs; + + rs =3D malloc(sizeof(*rs)); + if (!rs) + return NULL; + + rs->valid_bytes =3D 0; + rs->source =3D fopen(path_to_file, "rb"); + if (!rs->source) { + free(rs); + return NULL; + } + + if (fseek(rs->source, 0, SEEK_END)) { + fclose(rs->source); + free(rs); + return NULL; + } + rs->source_size =3D ftell(rs->source); + + if (fseek(rs->source, 0, SEEK_SET)) { + fclose(rs->source); + free(rs); + return NULL; + } + + rs->buffer =3D malloc(cache_size); + if (!rs->buffer) { + fclose(rs->source); + free(rs); + return NULL; + } + rs->buffer_size =3D cache_size; + return rs; +} + +void destroy_rand_stream(struct rand_stream *rs) +{ + fclose(rs->source); + free(rs->buffer); + free(rs); +} + +int next_byte(struct rand_stream *rs, char *ret) +{ + int res; + + if (rs->buffer_pos >=3D rs->valid_bytes) { + res =3D refill(rs); + if (res) + return res; + if (rs->valid_bytes =3D=3D 0) + return STREAM_EOF; + } + *ret =3D rs->buffer[rs->buffer_pos++]; + return 0; +} diff --git a/tools/kfuzztest-bridge/rand_stream.h b/tools/kfuzztest-bridge/= rand_stream.h new file mode 100644 index 000000000000..acb3271d30ca --- /dev/null +++ b/tools/kfuzztest-bridge/rand_stream.h @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Implements a cached file-reader for iterating over a byte stream of + * pseudo-random data + * + * Copyright 2025 Google LLC + */ +#ifndef KFUZZTEST_BRIDGE_RAND_STREAM_H +#define KFUZZTEST_BRIDGE_RAND_STREAM_H + +#include +#include + +#define STREAM_EOF 1 + +/** + * struct rand_stream - a buffered bytestream reader + * + * Reads and returns bytes from a file, using buffered pre-fetching to amo= rtize + * the cost of reads. + */ +struct rand_stream { + FILE *source; + size_t source_size; + char *buffer; + size_t buffer_size; + size_t buffer_pos; + size_t valid_bytes; +}; + +/** + * new_rand_stream - return a new struct rand_stream + * + * @path_to_file: source of the output byte stream. + * @cache_size: size of the read-ahead cache in bytes. + */ +struct rand_stream *new_rand_stream(const char *path_to_file, size_t cache= _size); + +/** + * destroy_rand_stream - clean up a rand stream's resources + * + * @rs: a struct rand_stream + */ +void destroy_rand_stream(struct rand_stream *rs); + +/** + * next_byte - return the next byte from a struct rand_stream + * + * @rs: an initialized struct rand_stream. + * @ret: return pointer. + * + * @return 0 on success or a negative value on failure. + * + */ +int next_byte(struct rand_stream *rs, char *ret); + +#endif /* KFUZZTEST_BRIDGE_RAND_STREAM_H */ --=20 2.51.0.384.g4c02a37b29-goog From nobody Thu Oct 2 14:21:31 2025 Received: from mail-wr1-f53.google.com (mail-wr1-f53.google.com [209.85.221.53]) (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 3196B299ABF for ; Tue, 16 Sep 2025 09:01:25 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.53 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758013289; cv=none; b=IA7gwFQ3ZSU3lAlcVtSwpA7AzjDZMEb6RAYBsy2pYLIYUyfcy8M6GPqYCh+JNiZMXosC6mlK7EYPa06xUqe3mAgqVj332iXkxdCFileF143b6n3mE911VzVrBc7Moz7ylqMW9xYmNCsZlW1utgdMlScWGRa201ECRqA4EMq07Nc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758013289; c=relaxed/simple; bh=uhEtHdh63cPeiXoMZi1g/dScwxBhozHastG/zAybSjc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Dzq00a8DidZVhb7Hi4Z3/Rq1AB8XLUi7VkofRmUNr5oPyJAthzgdb6o4U0RLKldsC7caeUdYos/pxeO8N0F6xXZMAOZXCgHbg1wmQnyswEXjfNKIL6QcH9tQ9KxFtutsqqXutjq2YKV9cRHrGwVgTcT/2Bbq/m3+YbHWXyoC0bg= 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=VhJPa0S/; arc=none smtp.client-ip=209.85.221.53 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="VhJPa0S/" Received: by mail-wr1-f53.google.com with SMTP id ffacd0b85a97d-3c6abcfd142so2527612f8f.2 for ; Tue, 16 Sep 2025 02:01:25 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1758013284; x=1758618084; 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=gp0wKR4UFIJ4rSEY85b8LtOArZzZXgf5YyWmJJ8KcKg=; b=VhJPa0S/okgnkjWMOhm7eNXl8qnILG6ptu29MHXcywdBsE7z7oys+48oVZBM9DgrXJ h9XrZbaAdqHL9lUPCtvN6iIRGB3LFqTbIpRlq8XDWIa0zBUpIe+WCcjl6x+O6HDmIVpq nMllO4/MFOgNCJ280MZhlVHowjhZPyuED6o5vjIhuYOUS3tdkfXGCnZauzV8/T+ctulG L/rg+NGZnQICOf0dtsBEhB8UxommjUdnTp9MLcQG8WnWhev+bZeAot6UBXsK7rna8Wjy 04qE/bTlHyVDR++O1m6VqYoYHYqQvRvX7IARdhaZzc8Zz/hyD+4L/E07J7gnwE/4cI49 mRnw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1758013284; x=1758618084; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=gp0wKR4UFIJ4rSEY85b8LtOArZzZXgf5YyWmJJ8KcKg=; b=P+KJ0VCWFAnr2jNbewK48b9TrQgvUulw/sckw7NuINmUUkEY708KGnEfilLhno49St HNmK9LlrIAjyXQovmXBD8AMByJ11gGL8FgpWFbEqdZkjEDl7ffNE7bApzPdFgvdaao0J G5WZAYf21AE73z4JphJKZ2ep+IDBoZxmqBytGNWv6z+YvvYVyTkbCj5G0SKihwm4uFEN ZEiQOlPSd5popXtAarRK/62MF6yMCQ6aaEceQ3iAtrFElA2yxOlnPsONV7/05xNsNiLI fAbl+vYuoZ5bR2TGGlEwAqgAB5BX6TEtcfK4UzGg/gL73IK99sCmh9Hv0lWtWdYDweMf M2RQ== X-Forwarded-Encrypted: i=1; AJvYcCVE1jjJ2AGZBVLO4rnoU+qkygcZLKVB06L/PC6MklKgEf1T87h8OdDum+dEvRGw/dDt0otTB72ij8YWzno=@vger.kernel.org X-Gm-Message-State: AOJu0Ywk9EXIMJUC3n/+O3XxrBATtBq82qvEK5OrT0/GYD3WVBMsb8D9 GCxZnWYkwjxYmzo5ParJ8XagN9RehrGAiFl/56cz9+5ZIHIHkuvRtL9Q X-Gm-Gg: ASbGnctqhW8fLgjafAAvEEnlYzAJksgwWIDbci8urhKdK3s2PhpVHatDQrjn2FZ13B1 4jQqk3mPBK6nbWqZBryQg8RsCNAyQqMMjOhfNOAr1cVwf5kuSfvDPPSHDOJV1Yl169tVdYxLGYn mV/it1No+JzAo2+K2eptedDNAL/B/ktc/rOx00hxMV9ImzJIlqpabAdZL+BalKabRN0vwwnPakj aLioNJtsPyjqezF05g3x7Q/KvH1pK4097E/oBUurnw9T5uV0iUevAipMr2eS1RlOXcPOtkVqHP/ /bi/rWOdfqM7EatZM15c4CwpB25Gq05vu2ftBj7dWMad3REV9q/mIFIfYQhuHxzO+joDLEnr2qF vG6cOzCIw1vxIWV1WHemKlEZ2AsS5Mdq4M10QsWXX+7CzB86M/SGLP5xonBVKoOvnWywCJ/S+j+ vBEbybAEGcFhEGmNXFyT+Eo9A= X-Google-Smtp-Source: AGHT+IECYEVT+4NUMePnPgixjF27SaVvTspbPgjy11IykssMepW6K3zQTMZ+/tQgCpK6vho4jpgLTw== X-Received: by 2002:a5d:4105:0:b0:3e9:d54:199f with SMTP id ffacd0b85a97d-3e90d541c27mr4925951f8f.32.1758013283923; Tue, 16 Sep 2025 02:01:23 -0700 (PDT) Received: from xl-nested.c.googlers.com.com (42.16.79.34.bc.googleusercontent.com. [34.79.16.42]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-45e037186e5sm212975035e9.5.2025.09.16.02.01.22 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 16 Sep 2025 02:01:23 -0700 (PDT) From: Ethan Graham To: ethangraham@google.com, glider@google.com Cc: andreyknvl@gmail.com, andy@kernel.org, brauner@kernel.org, brendan.higgins@linux.dev, davem@davemloft.net, davidgow@google.com, dhowells@redhat.com, dvyukov@google.com, elver@google.com, herbert@gondor.apana.org.au, ignat@cloudflare.com, jack@suse.cz, jannh@google.com, johannes@sipsolutions.net, kasan-dev@googlegroups.com, kees@kernel.org, kunit-dev@googlegroups.com, linux-crypto@vger.kernel.org, linux-kernel@vger.kernel.org, linux-mm@kvack.org, lukas@wunner.de, rmoar@google.com, shuah@kernel.org, tarasmadan@google.com Subject: [PATCH v1 05/10] kfuzztest: add ReST documentation Date: Tue, 16 Sep 2025 09:01:04 +0000 Message-ID: <20250916090109.91132-6-ethan.w.s.graham@gmail.com> X-Mailer: git-send-email 2.51.0.384.g4c02a37b29-goog In-Reply-To: <20250916090109.91132-1-ethan.w.s.graham@gmail.com> References: <20250916090109.91132-1-ethan.w.s.graham@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" From: Ethan Graham Add Documentation/dev-tools/kfuzztest.rst and reference it in the dev-tools index. Signed-off-by: Ethan Graham Acked-by: Alexander Potapenko --- v3: - Fix some typos and reword some sections. - Correct kfuzztest-bridge grammar description. - Reference documentation in kfuzztest-bridge/input_parser.c header comment. v2: - Add documentation for kfuzztest-bridge tool introduced in patch 4. --- --- Documentation/dev-tools/index.rst | 1 + Documentation/dev-tools/kfuzztest.rst | 385 ++++++++++++++++++++++++++ tools/kfuzztest-bridge/input_parser.c | 2 + 3 files changed, 388 insertions(+) create mode 100644 Documentation/dev-tools/kfuzztest.rst diff --git a/Documentation/dev-tools/index.rst b/Documentation/dev-tools/in= dex.rst index 65c54b27a60b..00ccc4da003b 100644 --- a/Documentation/dev-tools/index.rst +++ b/Documentation/dev-tools/index.rst @@ -32,6 +32,7 @@ Documentation/process/debugging/index.rst kfence kselftest kunit/index + kfuzztest ktap checkuapi gpio-sloppy-logic-analyzer diff --git a/Documentation/dev-tools/kfuzztest.rst b/Documentation/dev-tool= s/kfuzztest.rst new file mode 100644 index 000000000000..2dfa50f35a01 --- /dev/null +++ b/Documentation/dev-tools/kfuzztest.rst @@ -0,0 +1,385 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. Copyright 2025 Google LLC + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +Kernel Fuzz Testing Framework (KFuzzTest) +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Overview +=3D=3D=3D=3D=3D=3D=3D=3D + +The Kernel Fuzz Testing Framework (KFuzzTest) is a framework designed to e= xpose +internal kernel functions to a userspace fuzzing engine. + +It is intended for testing stateless or low-state functions that are diffi= cult +to reach from the system call interface, such as routines involved in file +format parsing or complex data transformations. This provides a method for +in-situ fuzzing of kernel code without requiring that it be built as a sep= arate +userspace library or that its dependencies be stubbed out. + +The framework consists of four main components: + +1. An API, based on the ``FUZZ_TEST`` macro, for defining test targets + directly in the kernel tree. +2. A binary serialization format for passing complex, pointer-rich data + structures from userspace to the kernel. +3. A ``debugfs`` interface through which a userspace fuzzer submits + serialized test inputs. +4. Metadata embedded in dedicated ELF sections of the ``vmlinux`` binary = to + allow for the discovery of available fuzz targets by external tooling. + +.. warning:: + KFuzzTest is a debugging and testing tool. It exposes internal kernel + functions to userspace with minimal sanitization and is designed for + use in controlled test environments only. It must **NEVER** be enabled + in production kernels. + +Supported Architectures +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +KFuzzTest is designed for generic architecture support. It has only been +explicitly tested on x86_64. + +Usage +=3D=3D=3D=3D=3D + +To enable KFuzzTest, configure the kernel with:: + + CONFIG_KFUZZTEST=3Dy + +which depends on ``CONFIG_DEBUGFS`` for receiving userspace inputs, and +``CONFIG_DEBUG_KERNEL`` as an additional guardrail for preventing KFuzzTest +from finding its way into a production build accidentally. + +The KFuzzTest sample fuzz targets can be built in with +``CONFIG_SAMPLE_KFUZZTEST``. + +KFuzzTest currently only supports targets that are built into the kernel, = as the +core module's startup process discovers fuzz targets from a dedicated ELF +section during startup. Furthermore, constraints and annotations emit meta= data +that can be scanned from a ``vmlinux`` binary by a userspace fuzzing engin= e. + +Declaring a KFuzzTest target +---------------------------- + +A fuzz target should be defined in a .c file. The recommended place to def= ine +this is under the subsystem's ``/tests`` directory in a ``_kfuz= z.c`` +file, following the convention used by KUnit. The only strict requirement = is +that the function being fuzzed is visible to the fuzz target. + +Defining a fuzz target involves three main parts: defining an input struct= ure, +writing the test body using the ``FUZZ_TEST`` macro, and optionally adding +metadata for the fuzzer. + +The following example illustrates how to create a fuzz target for a functi= on +``int process_data(const char *data, size_t len)``. + +.. code-block:: c + + /* + * 1. Define a struct to model the inputs for the function under test. + * Each field corresponds to an argument needed by the function. + */ + struct process_data_inputs { + const char *data; + size_t len; + }; + + /* + * 2. Define the fuzz target using the FUZZ_TEST macro. + * The first parameter is a unique name for the target. + * The second parameter is the input struct defined above. + */ + FUZZ_TEST(test_process_data, struct process_data_inputs) + { + /* + * Within this body, the 'arg' variable is a pointer to a + * fully initialized 'struct process_data_inputs'. + */ + + /* + * 3. (Optional) Add constraints to define preconditions. + * This check ensures 'arg->data' is not NULL. If the condition + * is not met, the test exits early. This also creates metadata + * to inform the fuzzer. + */ + KFUZZTEST_EXPECT_NOT_NULL(process_data_inputs, data); + + /* + * 4. (Optional) Add annotations to provide semantic hints to the + * fuzzer. This annotation informs the fuzzer that the 'len' field is + * the length of the buffer pointed to by 'data'. Annotations do not + * add any runtime checks. + */ + KFUZZTEST_ANNOTATE_LEN(process_data_inputs, len, data); + + /* + * 5. Call the kernel function with the provided inputs. + * Memory errors like out-of-bounds accesses on 'arg->data' will + * be detected by KASAN or other memory error detection tools. + */ + process_data(arg->data, arg->len); + } + +KFuzzTest provides two families of macros to improve the quality of fuzzin= g: + +- ``KFUZZTEST_EXPECT_*``: These macros define constraints, which are + preconditions that must be true for the test to proceed. They are enforc= ed + with a runtime check in the kernel. If a check fails, the current test r= un is + aborted. This metadata helps the userspace fuzzer avoid generating inval= id + inputs. + +- ``KFUZZTEST_ANNOTATE_*``: These macros define annotations, which are pur= ely + semantic hints for the fuzzer. They do not add any runtime checks and ex= ist + only to help the fuzzer generate more intelligent and structurally corre= ct + inputs. For example, KFUZZTEST_ANNOTATE_LEN links a size field to a poin= ter + field, which is a common pattern in C APIs. + +Metadata +-------- + +Macros ``FUZZ_TEST``, ``KFUZZTEST_EXPECT_*`` and ``KFUZZTEST_ANNOTATE_*`` = embed +metadata into several sections within the main ``.data`` section of the fi= nal +``vmlinux`` binary; ``.kfuzztest_target``, ``.kfuzztest_constraint`` and +``.kfuzztest_annotation`` respectively. + +This serves two purposes: + +1. The core module uses the ``.kfuzztest_target`` section at boot to disco= ver + every ``FUZZ_TEST`` instance and create its ``debugfs`` directory and + ``input`` file. +2. Userspace fuzzers can read this metadata from the ``vmlinux`` binary to + discover targets and learn about their rules and structure in order to + generate correct and effective inputs. + +The metadata in the ``.kfuzztest_*`` sections consists of arrays of fixed-= size C +structs (e.g., ``struct kfuzztest_target``). Fields within these structs t= hat +are pointers, such as ``name`` or ``arg_type_name``, contain addresses that +point to other locations in the ``vmlinux`` binary. A userspace tool that +parsing the ELF file must resolve these pointers to read the data that they +reference. For example, to get a target's name, a tool must: + +1. Read the ``struct kfuzztest_target`` from the ``.kfuzztest_target`` sec= tion. +2. Read the address in the ``.name`` field. +3. Use that address to locate and read null-terminated string from its pos= ition + elsewhere in the binary (e.g., ``.rodata``). + +Tooling Dependencies +-------------------- + +For userspace tools to parse the ``vmlinux`` binary and make use of emitted +KFuzzTest metadata, the kernel must be compiled with DWARF debug informati= on. +This is required for tools to understand the layout of C structs, resolve = type +information, and correctly interpret constraints and annotations. + +When using KFuzzTest with automated fuzzing tools, either +``CONFIG_DEBUG_INFO_DWARF4`` or ``CONFIG_DEBUG_INFO_DWARF5`` should be ena= bled. + +Input Format +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +KFuzzTest targets receive their inputs from userspace via a write to a ded= icated +debugfs file ``/sys/kernel/debug/kfuzztest//input``. + +The data written to this file must be a single binary blob that follows a +specific serialization format. This format is designed to allow complex, +pointer-rich C structures to be represented in a flat buffer, requiring on= ly a +single kernel allocation and copy from userspace. + +An input is first prefixed by an 8-byte header containing a magic value in= the +first four bytes, defined as ``KFUZZTEST_HEADER_MAGIC`` in +```, and a version number in the subsequent four +bytes. + +Version 0 +--------- + +In version 0 (i.e., when the version number in the 8-byte header is equal = to 0), +the input format consists of three main parts laid out sequentially: a reg= ion +array, a relocation table, and the payload.:: + + +----------------+---------------------+-----------+----------------+ + | region array | relocation table | padding | payload | + +----------------+---------------------+-----------+----------------+ + +Region Array +^^^^^^^^^^^^ + +This component is a header that describes how the raw data in the Payload = is +partitioned into logical memory regions. It consists of a count of regions +followed by an array of ``struct reloc_region``, where each entry defines a +single region with its size and offset from the start of the payload. + +.. code-block:: c + + struct reloc_region { + uint32_t offset; + uint32_t size; + }; + + struct reloc_region_array { + uint32_t num_regions; + struct reloc_region regions[]; + }; + +By convention, region 0 represents the top-level input struct that is pass= ed +as the arg variable to the ``FUZZ_TEST`` body. Subsequent regions typically +represent data buffers or structs pointed to by fields within that struct. +Region array entries must be ordered by ascending offset, and must not ove= rlap +with one another. + +Relocation Table +^^^^^^^^^^^^^^^^ + +The relocation table contains the instructions for the kernel to "hydrate"= the +payload by patching pointer fields. It contains an array of +``struct reloc_entry`` items. Each entry acts as a linking instruction, +specifying: + +- The location of a pointer that needs to be patched (identified by a regi= on + ID and an offset within that region). + +- The target region that the pointer should point to (identified by the + target's region ID) or ``KFUZZTEST_REGIONID_NULL`` if the pointer is ``N= ULL``. + +This table also specifies the amount of padding between its end and the st= art +of the payload, which should be at least 8 bytes. + +.. code-block:: c + + struct reloc_entry { + uint32_t region_id; + uint32_t region_offset; + uint32_t value; + }; + + struct reloc_table { + uint32_t num_entries; + uint32_t padding_size; + struct reloc_entry entries[]; + }; + +Payload +^^^^^^^ + +The payload contains the raw binary data for all regions, concatenated tog= ether +according to their specified offsets. + +- Region specific alignment: The data for each individual region must star= t at + an offset that is aligned to its own C type's requirements. For example,= a + ``uint64_t`` must begin on an 8-byte boundary. + +- Minimum alignment: The offset of each region, as well as the beginning o= f the + payload, must also be a multiple of the overall minimum alignment value.= This + value is determined by the greater of ``ARCH_KMALLOC_MINALIGN`` and + ``KASAN_GRANULE_SIZE`` (which is represented by ``KFUZZTEST_POISON_SIZE`= ` in + ``/include/linux/kfuzztest.h``). This minimum alignment ensures that all + function inputs respect C calling conventions. + +- Padding: The space between the end of one region's data and the beginnin= g of + the next must be sufficient for padding. The padding must also be at lea= st + the same minimum alignment value mentioned above. This is crucial for KA= SAN + builds, as it allows KFuzzTest to poison this unused space enabling prec= ise + detection of out-of-bounds memory accesses between adjacent buffers. + +The minimum alignment value is architecture-dependent and is exposed to +userspace via the read-only file +``/sys/kernel/debug/kfuzztest/_config/minalign``. The framework relies on +userspace tooling to construct the payload correctly, adhering to all thre= e of +these rules for every region. + +KFuzzTest Bridge Tool +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +The ``kfuzztest-bridge`` program is a userspace utility that encodes a ran= dom +byte stream into the structured binary format expected by a KFuzzTest harn= ess. +It allows users to describe the target's input structure textually, making= it +easy to perform smoke tests or connect harnesses to blob-based fuzzing eng= ines. + +This tool is intended to be simple, both in usage and implementation. Its +structure and DSL are sufficient for simpler use-cases. For more advanced +coverage-guided fuzzing it is recommended to use +`syzkaller ` which implements deeper +support for KFuzzTest targets. + +Usage +----- + +The tool can be built with ``make tools/kfuzztest-bridge``. In the case of= libc +incompatibilities, the tool will have to be linked statically or built on = the +target system. + +Example: + +.. code-block:: sh + + ./kfuzztest-bridge \ + "foo { u32 ptr[bar] }; bar { ptr[data] len[data, u64]}; data { arr= [u8, 42] };" \ + "my-fuzz-target" /dev/urandom + +The command takes three arguments + +1. A string describing the input structure (see `Textual Format`_ sub-sec= tion). +2. The name of the target test, which corresponds to its directory in + ``/sys/kernel/debug/kfuzztest/``. +3. A path to a file providing a stream of random data, such as + ``/dev/urandom``. + +The structure string in the example corresponds to the following C data +structures: + +.. code-block:: c + + struct foo { + u32 a; + struct bar *b; + }; + + struct bar { + struct data *d; + u64 data_len; /* Equals 42. */ + }; + + struct data { + char arr[42]; + }; + +Textual Format +-------------- + +The textual format is a human-readable representation of the region-based = binary +format used by KFuzzTest. It is described by the following grammar: + +.. code-block:: text + + schema ::=3D region ( ";" region )* [";"] + region ::=3D identifier "{" type ( " " type )* "}" + type ::=3D primitive | pointer | array | length | string + primitive ::=3D "u8" | "u16" | "u32" | "u64" + pointer ::=3D "ptr" "[" identifier "]" + array ::=3D "arr" "[" primitive "," integer "]" + length ::=3D "len" "[" identifier "," primitive "]" + string ::=3D "str" "[" integer "]" + identifier ::=3D [a-zA-Z_][a-zA-Z1-9_]* + integer ::=3D [0-9]+ + +Pointers must reference a named region. + +To fuzz a raw buffer, the buffer must be defined in its own region, as sho= wn +below: + +.. code-block:: c + + struct my_struct { + char *buf; + size_t buflen; + }; + +This would correspond to the following textual description: + +.. code-block:: text + + my_struct { ptr[buf] len[buf, u64] }; buf { arr[u8, n] }; + +Here, ``n`` is some integer value defining the size of the byte array insi= de of +the ``buf`` region. diff --git a/tools/kfuzztest-bridge/input_parser.c b/tools/kfuzztest-bridge= /input_parser.c index 61d324b9dc0e..e07dcb4d21cc 100644 --- a/tools/kfuzztest-bridge/input_parser.c +++ b/tools/kfuzztest-bridge/input_parser.c @@ -16,6 +16,8 @@ * and its corresponding length encoded over 8 bytes, where `buf` itself * contains a 42-byte array. * + * The full grammar is documented in Documentation/dev-tools/kfuzztest.rst. + * * Copyright 2025 Google LLC */ #include --=20 2.51.0.384.g4c02a37b29-goog From nobody Thu Oct 2 14:21:31 2025 Received: from mail-wm1-f52.google.com (mail-wm1-f52.google.com [209.85.128.52]) (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 DED1929B204 for ; Tue, 16 Sep 2025 09:01:27 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.52 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758013290; cv=none; b=ntnQwykfIvYlFQrzsOp+RHE3ByC/FmDrlAZV7IeRtPPH1EmQWY0P+cMJJ85YeOEJmfSKHm+AEArZvAezL8PnRqjuFEVnBPVs85sW3809tA557KmgieXro2oMIcIpWUenp6M+kb4C8C4LxdmsP+9a8rtnq+3gA2ih30tIGs13yks= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758013290; c=relaxed/simple; bh=6hHHbFntCUYSD3HBZLJcRkhdW7KpSm3FgA41veMYbnA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=eOH9/1mjQS4KfxEXE6opkGyQ4J4fs1Y0mjuOst4GnBgWRWuEMjtWPOKN5bcbWXapPteUfyCcNYpkQ6mxz5YCBy4opJx/OxEpei1x5XqOxrk+10MG0syau0v/n1K4MJ1h8re5zdnqZpsDYPEbDD0tWE3yGlGMiXdzesQ+goko7Z0= 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=E89ov6kE; arc=none smtp.client-ip=209.85.128.52 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="E89ov6kE" Received: by mail-wm1-f52.google.com with SMTP id 5b1f17b1804b1-45df0cde41bso38101005e9.3 for ; Tue, 16 Sep 2025 02:01:27 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1758013286; x=1758618086; 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=xdmcBIJJ/dIrmboz2dj1USPN5mnOnIS9n3oFxB4eaBc=; b=E89ov6kEPJR1H4p5K3KteNzcEruBDSpuqzB/I2CNsZfLVymoTGmOr6ISsEI1FBqYFK 8n6XaNzI/doOn/EWtnZjh2JiV9UBC+KyInwr6KZZD7blGK0b+inTCw380reab8cEk4g0 3mQvhDY2TiZt99HjqBtc+sGAeZNAuhkzKR+tMYh0PUrLVJgenjM3feSJ9c+0RFUbbmJI BvnbCT9qCEmFpmoUfCe/WPyux6Y2Exw7uLC6nWrk05zfMMpjJ96fgE4NLPQ+pXOcGmVJ dOocydVLBu7AaKAcJFyOzqvNYC7mS/kEud8XpkCBrzncRRmx2TPEyL8TZH2OTKKp4jnM eVfQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1758013286; x=1758618086; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=xdmcBIJJ/dIrmboz2dj1USPN5mnOnIS9n3oFxB4eaBc=; b=t/Cb2W5m976HsXdCBA//NVvOS/A21xqmU49b+429DRp1WTXBeLkWDmV/lrPhExm0Yh 0uLrRI4Pyjd58Igy32hMQqlviBU48Hwjd2Afdx7ykO7Wn1UAoautQFsW7+d5lnthkC8y z4EVHclRh2eS5LY17p2AkXNQUWAIsnvJguChMHAcQw3Iob6ZDw/TUcRHb0KuW1t6YFVs 9lbH6yGOMGs9Migd3PKA3uIyAGclt9BWxTx45RxJkJ9gOodLzlTtkIkuXJNm0PUrx/t9 rLHn4uQu9lQeF1FIjGtngdS0Ejb8paapsN65Ssz8o69d0L9xM7p1vop/hgC7Hbna/HqA 9JfA== X-Forwarded-Encrypted: i=1; AJvYcCWvk7n1BkARFonmJr/u7iwoNjJ2uoMU6u2dMv3FZ2PmFZ0Ty6PXHqItaaRUB8VDArkytZbPlYGnDcDQyPM=@vger.kernel.org X-Gm-Message-State: AOJu0YwtYVthThD0/BUEQyBAmviYRyRsO5Xtnn7Fu4qrN1LN7f/VGAXI KhbBEJaw/I89MDXfaUJwcQPEMgXWTRq5SZOOhcfUOu+JCoerSGZKf/bJ X-Gm-Gg: ASbGncuTn+mvvNGRcp+PdYMGFTP+FXyZuihhVLtwA0u7gp8CaUXGMCHiRr9yp4xKdqv GpZ7VBlrMApHVKn10/YwWtoQ7UFmOChd4YNugA49tlbB2IEhfXr81SkfPGbJhu1+hSevHwqPute vklXYPXo7+h9BiIghJXoWmCiO6z1nN1Sd5E4FKKJNQdQh+qwl9YijxOWVV1JjqAeyL9KEVDbz5e UKi65iVQxm5TXplAOcUgcpEexP/QSBe/+zS0jNUgYvcabSSWgqM2XpmxuD10yyoM53dBf4Dpdn4 pytw6GUkIQ7stNhoGk/UTq5sl3gh3kUEUOSucZAxNM0S9x7ZO2o6cO/pspSCAKac55ugv/umsXo 7f57OmFR2qWhE0m8TkDurMsxYHqVZe5zx0iujcaOjwp2MrAig7E7pvWRWl+AFfzEmOJsDr3atL6 xHr1Yxytgni5vp X-Google-Smtp-Source: AGHT+IEadfbP3jNYyOIN5QPnIDkgoE5rEixgqXRhdDXaUoboFgkfVk5OQ6KluvW7vDOuiRH8ZlKDbQ== X-Received: by 2002:a05:600c:12c8:b0:459:d645:bff7 with SMTP id 5b1f17b1804b1-45f211d075emr98840705e9.12.1758013285667; Tue, 16 Sep 2025 02:01:25 -0700 (PDT) Received: from xl-nested.c.googlers.com.com (42.16.79.34.bc.googleusercontent.com. [34.79.16.42]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-45e037186e5sm212975035e9.5.2025.09.16.02.01.24 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 16 Sep 2025 02:01:24 -0700 (PDT) From: Ethan Graham To: ethangraham@google.com, glider@google.com Cc: andreyknvl@gmail.com, andy@kernel.org, brauner@kernel.org, brendan.higgins@linux.dev, davem@davemloft.net, davidgow@google.com, dhowells@redhat.com, dvyukov@google.com, elver@google.com, herbert@gondor.apana.org.au, ignat@cloudflare.com, jack@suse.cz, jannh@google.com, johannes@sipsolutions.net, kasan-dev@googlegroups.com, kees@kernel.org, kunit-dev@googlegroups.com, linux-crypto@vger.kernel.org, linux-kernel@vger.kernel.org, linux-mm@kvack.org, lukas@wunner.de, rmoar@google.com, shuah@kernel.org, tarasmadan@google.com Subject: [PATCH v1 06/10] kfuzztest: add KFuzzTest sample fuzz targets Date: Tue, 16 Sep 2025 09:01:05 +0000 Message-ID: <20250916090109.91132-7-ethan.w.s.graham@gmail.com> X-Mailer: git-send-email 2.51.0.384.g4c02a37b29-goog In-Reply-To: <20250916090109.91132-1-ethan.w.s.graham@gmail.com> References: <20250916090109.91132-1-ethan.w.s.graham@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" From: Ethan Graham Add two simple fuzz target samples to demonstrate the KFuzzTest API and provide basic self-tests for the framework. These examples showcase how a developer can define a fuzz target using the FUZZ_TEST(), constraint, and annotation macros, and serve as runtime sanity checks for the core logic. For example, they test that out-of-bounds memory accesses into poisoned padding regions are correctly detected in a KASAN build. These have been tested by writing syzkaller-generated inputs into their debugfs 'input' files and verifying that the correct KASAN reports were triggered. Signed-off-by: Ethan Graham Acked-by: Alexander Potapenko --- samples/Kconfig | 7 ++ samples/Makefile | 1 + samples/kfuzztest/Makefile | 3 + samples/kfuzztest/overflow_on_nested_buffer.c | 71 +++++++++++++++++++ samples/kfuzztest/underflow_on_buffer.c | 59 +++++++++++++++ 5 files changed, 141 insertions(+) create mode 100644 samples/kfuzztest/Makefile create mode 100644 samples/kfuzztest/overflow_on_nested_buffer.c create mode 100644 samples/kfuzztest/underflow_on_buffer.c diff --git a/samples/Kconfig b/samples/Kconfig index 6e072a5f1ed8..5209dd9d7a5c 100644 --- a/samples/Kconfig +++ b/samples/Kconfig @@ -320,6 +320,13 @@ config SAMPLE_HUNG_TASK Reading these files with multiple processes triggers hung task detection by holding locks for a long time (256 seconds). =20 +config SAMPLE_KFUZZTEST + bool "Build KFuzzTest sample targets" + depends on KFUZZTEST + help + Build KFuzzTest sample targets that serve as selftests for input + deserialization and inter-region redzone poisoning logic. + source "samples/rust/Kconfig" =20 source "samples/damon/Kconfig" diff --git a/samples/Makefile b/samples/Makefile index 07641e177bd8..3a0e7f744f44 100644 --- a/samples/Makefile +++ b/samples/Makefile @@ -44,4 +44,5 @@ obj-$(CONFIG_SAMPLE_DAMON_WSSE) +=3D damon/ obj-$(CONFIG_SAMPLE_DAMON_PRCL) +=3D damon/ obj-$(CONFIG_SAMPLE_DAMON_MTIER) +=3D damon/ obj-$(CONFIG_SAMPLE_HUNG_TASK) +=3D hung_task/ +obj-$(CONFIG_SAMPLE_KFUZZTEST) +=3D kfuzztest/ obj-$(CONFIG_SAMPLE_TSM_MR) +=3D tsm-mr/ diff --git a/samples/kfuzztest/Makefile b/samples/kfuzztest/Makefile new file mode 100644 index 000000000000..4f8709876c9e --- /dev/null +++ b/samples/kfuzztest/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_SAMPLE_KFUZZTEST) +=3D overflow_on_nested_buffer.o underflow_= on_buffer.o diff --git a/samples/kfuzztest/overflow_on_nested_buffer.c b/samples/kfuzzt= est/overflow_on_nested_buffer.c new file mode 100644 index 000000000000..2f1c3ff9f750 --- /dev/null +++ b/samples/kfuzztest/overflow_on_nested_buffer.c @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This file contains a KFuzzTest example target that ensures that a buffer + * overflow on a nested region triggers a KASAN OOB access report. + * + * Copyright 2025 Google LLC + */ + +/** + * DOC: test_overflow_on_nested_buffer + * + * This test uses a struct with two distinct dynamically allocated buffers. + * It checks that KFuzzTest's memory layout correctly poisons the memory + * regions and that KASAN can detect an overflow when reading one byte pas= t the + * end of the first buffer (`a`). + * + * It can be invoked with kfuzztest-bridge using the following command: + * + * ./kfuzztest-bridge \ + * "nested_buffers { ptr[a] len[a, u64] ptr[b] len[b, u64] }; \ + * a { arr[u8, 64] }; b { arr[u8, 64] };" \ + * "test_overflow_on_nested_buffer" /dev/urandom + * + * The first argument describes the C struct `nested_buffers` and specifie= s that + * both `a` and `b` are pointers to arrays of 64 bytes. + */ +#include + +static void overflow_on_nested_buffer(const char *a, size_t a_len, const c= har *b, size_t b_len) +{ + size_t i; + pr_info("a =3D [%px, %px)", a, a + a_len); + pr_info("b =3D [%px, %px)", b, b + b_len); + + /* Ensure that all bytes in arg->b are accessible. */ + for (i =3D 0; i < b_len; i++) + READ_ONCE(b[i]); + /* + * Check that all bytes in arg->a are accessible, and provoke an OOB on + * the first byte to the right of the buffer which will trigger a KASAN + * report. + */ + for (i =3D 0; i <=3D a_len; i++) + READ_ONCE(a[i]); +} + +struct nested_buffers { + const char *a; + size_t a_len; + const char *b; + size_t b_len; +}; + +/** + * The KFuzzTest input format specifies that struct nested buffers should + * be expanded as: + * + * | a | b | pad[8] | *a | pad[8] | *b | + * + * where the padded regions are poisoned. We expect to trigger a KASAN rep= ort by + * overflowing one byte into the `a` buffer. + */ +FUZZ_TEST(test_overflow_on_nested_buffer, struct nested_buffers) +{ + KFUZZTEST_EXPECT_NOT_NULL(nested_buffers, a); + KFUZZTEST_EXPECT_NOT_NULL(nested_buffers, b); + KFUZZTEST_ANNOTATE_LEN(nested_buffers, a_len, a); + KFUZZTEST_ANNOTATE_LEN(nested_buffers, b_len, b); + + overflow_on_nested_buffer(arg->a, arg->a_len, arg->b, arg->b_len); +} diff --git a/samples/kfuzztest/underflow_on_buffer.c b/samples/kfuzztest/un= derflow_on_buffer.c new file mode 100644 index 000000000000..02704a1bfebb --- /dev/null +++ b/samples/kfuzztest/underflow_on_buffer.c @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This file contains a KFuzzTest example target that ensures that a buffer + * underflow on a region triggers a KASAN OOB access report. + * + * Copyright 2025 Google LLC + */ + +/** + * DOC: test_underflow_on_buffer + * + * This test ensures that the region between the metadata struct and the + * dynamically allocated buffer is poisoned. It provokes a one-byte underf= low + * on the buffer, which should be caught by KASAN. + * + * It can be invoked with kfuzztest-bridge using the following command: + * + * ./kfuzztest-bridge \ + * "some_buffer { ptr[buf] len[buf, u64]}; buf { arr[u8, 128] };" \ + * "test_underflow_on_buffer" /dev/urandom + * + * The first argument describes the C struct `some_buffer` and specifies t= hat + * `buf` is a pointer to an array of 128 bytes. The second argument is the= test + * name, and the third is a seed file. + */ +#include + +static void underflow_on_buffer(char *buf, size_t buflen) +{ + size_t i; + + pr_info("buf =3D [%px, %px)", buf, buf + buflen); + + /* First ensure that all bytes in arg->b are accessible. */ + for (i =3D 0; i < buflen; i++) + READ_ONCE(buf[i]); + /* + * Provoke a buffer overflow on the first byte preceding b, triggering + * a KASAN report. + */ + READ_ONCE(*((char *)buf - 1)); +} + +struct some_buffer { + char *buf; + size_t buflen; +}; + +/** + * Tests that the region between struct some_buffer and the expanded *buf = field + * is correctly poisoned by accessing the first byte before *buf. + */ +FUZZ_TEST(test_underflow_on_buffer, struct some_buffer) +{ + KFUZZTEST_EXPECT_NOT_NULL(some_buffer, buf); + KFUZZTEST_ANNOTATE_LEN(some_buffer, buflen, buf); + + underflow_on_buffer(arg->buf, arg->buflen); +} --=20 2.51.0.384.g4c02a37b29-goog From nobody Thu Oct 2 14:21:31 2025 Received: from mail-wm1-f42.google.com (mail-wm1-f42.google.com [209.85.128.42]) (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 18ED229B229 for ; Tue, 16 Sep 2025 09:01:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.42 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758013291; cv=none; b=uMRzaiCaHRG94bpMsdWuKlWcNj9Tzr2CkudDyvI8r7c5AU3gmJvffj0p7ZMnG9+1V1SHc+94qI1/NqJAJ4HMWmTEeRLFlMRDzdisgNMbr1b9bLHQmusi9LM3LPJ68ie3ScqyMpo6FiVUjBhdhgVRrD7AlXkj9OGWcBLnwUUPNXc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758013291; c=relaxed/simple; bh=5p5lfP6HUCJ6tM1PM09VVWmrB48/epMFUrdxYERWB00=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=U1IMICA7s/XpZkf95LZO5EuKKqV4vaOBTx7EwCH4Z8yRRJJLiayL3hkuUiQdyTEZCaGa4CfytZ5IFJ7eJyH9jJIsPS8gRC5trvU7dfR8KrQkJjsDgIzmBOdf/6bdDbK7lQ2djYWMYLlkDaiQKjai79lv2s62l/hac1gzapBB9rc= 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=eNhub0gK; arc=none smtp.client-ip=209.85.128.42 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="eNhub0gK" Received: by mail-wm1-f42.google.com with SMTP id 5b1f17b1804b1-45dec1ae562so47335425e9.1 for ; Tue, 16 Sep 2025 02:01:28 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1758013287; x=1758618087; 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=eX0GE1tFaVdaJ+X5gME1f6NzrpcsI0lAH1DHePnNN9I=; b=eNhub0gK53w/8PQl8swXPk2s3Pgx4dAqnxvF+BhC+yC3xIHLj604WLYHi/IgLjoLtR K9Swk5/TdaIAI5sKGeni7och0LAR3GOJGAVXQ5HDSJpltdrXu3OWlNdPzxbehmmSZXg5 +PzmpUJl53IYz0BankVyAahGr7KVBxI1plPHuq93lNRZNNl5jaRSHGow5Qhv/eKO+o7N rcJiUVfuQ52ZZfLPaz06nZKGzwMLnbTF9Hg74vjmlJT+8cPXQsxkxv5Y7M6H03jVcpUz ZHJi4QbJCD9fxyORvZ5tfbiVWwtQouY/eSfQZEq8cBhkjJU7Hj/1p6BJyteczbpStW5i XJ1g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1758013287; x=1758618087; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=eX0GE1tFaVdaJ+X5gME1f6NzrpcsI0lAH1DHePnNN9I=; b=sNCq3Zf8+MhStIdDQ9ZnLN5eYk4MiCJkKXbfglCDHzjvhqZ5rVqwQrpDg1r5hEF6p5 zic2/yeKUrJ5JILxBHnSaaf+40TwNO9wkrpnnh33R0gMCtd+Z26cC86Ue2uUK2tnrPFk qmHrgbXxRK/1VC3qV2GBBEyKAMsijJVW9lms7HOI3d5OWTdE9nEr1LYSFwMSfVOhaM11 O6IDJcVlxmpwg/cuQtPbEpl96nIjALJ8bCfSGrV2ejMF6UnZVUBA7jAAQPInOI953AxK ER3KC421NsgazDQYn/YJvDtQuEFEYly0jXsU9lFf74kvfUTUAaiyvqRa1rVlI5F7DmCJ J7lA== X-Forwarded-Encrypted: i=1; AJvYcCV7IPqE8kXwJF0T6M7nwwTjTTLsGDG3D/0qtgX41vpZj5hYyZ1p8acUp6C4iwXSHA//maqOKIJEm3eNwEE=@vger.kernel.org X-Gm-Message-State: AOJu0YwgjEdredrMw0v9Uiig/u+p0yyMjvI6AhC24j6xMhDQKXuk6dih 2QVoHLaLuJso+9pU9U9P4p9mmSwF1CuUNutSR61HnT6Ma3o3lZrCYRXM X-Gm-Gg: ASbGncvdBwOdJUTbe0U8+aataep5+oF382iJAXg2Nr9ETxqRxGHlx8Vb53ZB0vJj0VO CaUHsM/OKGIFJgRMaTx7ujG/QxZ+VCpz0VnGIXBMZQO8zcdMUSK9iv72CR0fYj3sfS16sBRDt8G LIdx7RdO57uGSCQ3HlEkmULeCRKnBeR61YHH+gTWUNSdaYnpFKJx6zYrXQ+4Ucn5p9ozBLuxwGy a1UQa7DO3BsZ2RfFr4yt0oiQ8PBUQC7F9WLesXsYj/p5eP3uc31jdGFeUTJL1MSo4FI+viDblcJ JFYzKB9w4JbHavN02lQrO+DRiCL8GK1dsdmGUx7+tAWBYaYmJHsRBJQMH+Buq02ReisRFkNAaLw WofothkghJ0/r6Nx+0GBMv6v65AmyewXe599vzjXYVSciGgz3/24Q+P4tWtkW8DqW4o//mAvu6f WsA91yjpWf5Upet5ptvawnw2E= X-Google-Smtp-Source: AGHT+IG4rWU9dUsfNI/GTT2ZfR/FJQxnX0XoO3ls2sTIzkMGJlfRV3ER/lT2KNe6XIHz3tv6/6a9lw== X-Received: by 2002:a05:6000:2001:b0:3eb:86fb:bcd9 with SMTP id ffacd0b85a97d-3eb86fbbfb9mr4536453f8f.12.1758013287060; Tue, 16 Sep 2025 02:01:27 -0700 (PDT) Received: from xl-nested.c.googlers.com.com (42.16.79.34.bc.googleusercontent.com. [34.79.16.42]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-45e037186e5sm212975035e9.5.2025.09.16.02.01.25 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 16 Sep 2025 02:01:26 -0700 (PDT) From: Ethan Graham To: ethangraham@google.com, glider@google.com Cc: andreyknvl@gmail.com, andy@kernel.org, brauner@kernel.org, brendan.higgins@linux.dev, davem@davemloft.net, davidgow@google.com, dhowells@redhat.com, dvyukov@google.com, elver@google.com, herbert@gondor.apana.org.au, ignat@cloudflare.com, jack@suse.cz, jannh@google.com, johannes@sipsolutions.net, kasan-dev@googlegroups.com, kees@kernel.org, kunit-dev@googlegroups.com, linux-crypto@vger.kernel.org, linux-kernel@vger.kernel.org, linux-mm@kvack.org, lukas@wunner.de, rmoar@google.com, shuah@kernel.org, tarasmadan@google.com Subject: [PATCH v1 07/10] crypto: implement KFuzzTest targets for PKCS7 and RSA parsing Date: Tue, 16 Sep 2025 09:01:06 +0000 Message-ID: <20250916090109.91132-8-ethan.w.s.graham@gmail.com> X-Mailer: git-send-email 2.51.0.384.g4c02a37b29-goog In-Reply-To: <20250916090109.91132-1-ethan.w.s.graham@gmail.com> References: <20250916090109.91132-1-ethan.w.s.graham@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" From: Ethan Graham Add KFuzzTest targets for pkcs7_parse_message, rsa_parse_pub_key, and rsa_parse_priv_key to serve as real-world examples of how the framework is used. These functions are ideal candidates for KFuzzTest as they perform complex parsing of user-controlled data but are not directly exposed at the syscall boundary. This makes them difficult to exercise with traditional fuzzing tools and showcases the primary strength of the KFuzzTest framework: providing an interface to fuzz internal functions. To validate the effectiveness of the framework on these new targets, we injected two artificial bugs and let syzkaller fuzz the targets in an attempt to catch them. The first of these was calling the asn1 decoder with an incorrect input from pkcs7_parse_message, like so: - ret =3D asn1_ber_decoder(&pkcs7_decoder, ctx, data, datalen); + ret =3D asn1_ber_decoder(&pkcs7_decoder, ctx, data, datalen + 1); The second was bug deeper inside of asn1_ber_decoder itself, like so: - for (len =3D 0; n > 0; n--) + for (len =3D 0; n >=3D 0; n--) syzkaller was able to trigger these bugs, and the associated KASAN slab-out-of-bounds reports, within seconds. The targets are defined within /lib/tests, alongside existing KUnit tests. Signed-off-by: Ethan Graham Reviewed-by: Ignat Korchagin --- v3: - Change the fuzz target build to depend on CONFIG_KFUZZTEST=3Dy, eliminating the need for a separate config option for each individual file as suggested by Ignat Korchagin. - Remove KFUZZTEST_EXPECT_LE on the length of the `key` field inside of the fuzz targets. A maximum length is now set inside of the core input parsing logic. v2: - Move KFuzzTest targets outside of the source files into dedicated _kfuzz.c files under /crypto/asymmetric_keys/tests/ as suggested by Ignat Korchagin and Eric Biggers. --- --- crypto/asymmetric_keys/Makefile | 2 + crypto/asymmetric_keys/tests/Makefile | 2 + crypto/asymmetric_keys/tests/pkcs7_kfuzz.c | 22 +++++++++++ .../asymmetric_keys/tests/rsa_helper_kfuzz.c | 38 +++++++++++++++++++ 4 files changed, 64 insertions(+) create mode 100644 crypto/asymmetric_keys/tests/Makefile create mode 100644 crypto/asymmetric_keys/tests/pkcs7_kfuzz.c create mode 100644 crypto/asymmetric_keys/tests/rsa_helper_kfuzz.c diff --git a/crypto/asymmetric_keys/Makefile b/crypto/asymmetric_keys/Makef= ile index bc65d3b98dcb..77b825aee6b2 100644 --- a/crypto/asymmetric_keys/Makefile +++ b/crypto/asymmetric_keys/Makefile @@ -67,6 +67,8 @@ obj-$(CONFIG_PKCS7_TEST_KEY) +=3D pkcs7_test_key.o pkcs7_test_key-y :=3D \ pkcs7_key_type.o =20 +obj-y +=3D tests/ + # # Signed PE binary-wrapped key handling # diff --git a/crypto/asymmetric_keys/tests/Makefile b/crypto/asymmetric_keys= /tests/Makefile new file mode 100644 index 000000000000..4ffe0bbe9530 --- /dev/null +++ b/crypto/asymmetric_keys/tests/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_KFUZZTEST) +=3D pkcs7_kfuzz.o +obj-$(CONFIG_KFUZZTEST) +=3D rsa_helper_kfuzz.o diff --git a/crypto/asymmetric_keys/tests/pkcs7_kfuzz.c b/crypto/asymmetric= _keys/tests/pkcs7_kfuzz.c new file mode 100644 index 000000000000..37e02ba517d8 --- /dev/null +++ b/crypto/asymmetric_keys/tests/pkcs7_kfuzz.c @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PKCS#7 parser KFuzzTest target + * + * Copyright 2025 Google LLC + */ +#include +#include + +struct pkcs7_parse_message_arg { + const void *data; + size_t datalen; +}; + +FUZZ_TEST(test_pkcs7_parse_message, struct pkcs7_parse_message_arg) +{ + KFUZZTEST_EXPECT_NOT_NULL(pkcs7_parse_message_arg, data); + KFUZZTEST_ANNOTATE_ARRAY(pkcs7_parse_message_arg, data); + KFUZZTEST_ANNOTATE_LEN(pkcs7_parse_message_arg, datalen, data); + + pkcs7_parse_message(arg->data, arg->datalen); +} diff --git a/crypto/asymmetric_keys/tests/rsa_helper_kfuzz.c b/crypto/asymm= etric_keys/tests/rsa_helper_kfuzz.c new file mode 100644 index 000000000000..bd29ed5e8c82 --- /dev/null +++ b/crypto/asymmetric_keys/tests/rsa_helper_kfuzz.c @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * RSA key extract helper KFuzzTest targets + * + * Copyright 2025 Google LLC + */ +#include +#include + +struct rsa_parse_pub_key_arg { + const void *key; + size_t key_len; +}; + +FUZZ_TEST(test_rsa_parse_pub_key, struct rsa_parse_pub_key_arg) +{ + KFUZZTEST_EXPECT_NOT_NULL(rsa_parse_pub_key_arg, key); + KFUZZTEST_ANNOTATE_ARRAY(rsa_parse_pub_key_arg, key); + KFUZZTEST_ANNOTATE_LEN(rsa_parse_pub_key_arg, key_len, key); + + struct rsa_key out; + rsa_parse_pub_key(&out, arg->key, arg->key_len); +} + +struct rsa_parse_priv_key_arg { + const void *key; + size_t key_len; +}; + +FUZZ_TEST(test_rsa_parse_priv_key, struct rsa_parse_priv_key_arg) +{ + KFUZZTEST_EXPECT_NOT_NULL(rsa_parse_priv_key_arg, key); + KFUZZTEST_ANNOTATE_ARRAY(rsa_parse_priv_key_arg, key); + KFUZZTEST_ANNOTATE_LEN(rsa_parse_priv_key_arg, key_len, key); + + struct rsa_key out; + rsa_parse_priv_key(&out, arg->key, arg->key_len); +} --=20 2.51.0.384.g4c02a37b29-goog From nobody Thu Oct 2 14:21:31 2025 Received: from mail-wm1-f47.google.com (mail-wm1-f47.google.com [209.85.128.47]) (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 2B9BF29B778 for ; Tue, 16 Sep 2025 09:01:29 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.47 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758013292; cv=none; b=dBISdDr2x8jaHKZCxc7HvzuIgUoj0Zlg5iCGFYUh1QOQzHxLMvqrZCFR6mIpHqK/teJ88LHflVrTfL53/5SvTg8kVGnVLiCscNJ/Nslz6suDfPFetArSY03CDRJhBUh3P7JIiY+4QPY2tCGkUaFVSw8l/Z10xiOaUyD7l8Dv1AY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758013292; c=relaxed/simple; bh=JJCw6mv16EeG+3BtXxjY/l2oWcIXNWBdkiWvUlFHp88=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=OoSF1rd/6WXrSl91stYMkvtMXq+D/NyG302ZA7S83+18rxuSguFGAe67eD/EYcqbA98QP1rcf71NjL+WvPNWyWXs1C+OawEztFyrHC9SC++kXeF2sdk5V9JPiMZO/ZXiKd3uk/ZpW+JscxnMuIxYuEHfY9O4lpHXNOidoR+gRDY= 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=N58pLXJ6; arc=none smtp.client-ip=209.85.128.47 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="N58pLXJ6" Received: by mail-wm1-f47.google.com with SMTP id 5b1f17b1804b1-45dd513f4ecso31004335e9.3 for ; Tue, 16 Sep 2025 02:01:29 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1758013288; x=1758618088; 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=DO9AFhAl9ilu83R9AZsAc7oWiIru4g9kqtmWSFQA0xE=; b=N58pLXJ6T0qHU3h2Zwf7fuXi6gHhTD6H1TRqgoxFYisWjb8hZg+7Mkeg0e4cMlY5FH PZuZo9hYshBC83rNwd0WYy6b+AtaFa9GrHZklM+8cdQXec15oatowoCs9l3MjheAaz9f 57sNME+3wnVwYHgcnR3LBfkAoDqGswjRbONRx3c5pYxpKJzOtNKbIUlczKwsi6PSCUZI aCNhd4HaJ8cllZnffPrOQUabKK9AhOq4/Z3YHRHUb+hQu7XdBcLwGIPSHWQALuzEbpli tlcUsUycnugwmAcW47dzMwul7hjxjYim29OQEHK5My0ddYRjfBlmxuzhTSoBbR/hyRGK F07w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1758013288; x=1758618088; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=DO9AFhAl9ilu83R9AZsAc7oWiIru4g9kqtmWSFQA0xE=; b=wVswapjarV3oyS5XdxB7N6hgKsk9ux+cGnXn37MwgeGyLMOonohyqJe7mo2LEUve9p 6jr/YvB7gd1V8KkwJ1wqv4PyDek41jRxt0aBnKiBD/bS2GFlNZnbQm7RecVgu86R49f4 fHQykGxDVGOXm20N0QCD1u19ROIdj3m3ShrFwSeUWCpOcXz9Lr+SstmY+vGrrGRz1Coq d1sBf2TSliUiVavQj6UpD9KTWzB91m0b5l6195QA0WxvGpwJXFTUR3Tmwa4w8uSryQyM h5Socj7lcGq6E5xmTAB5256uYjbLJcQDyQTCSzByILFLcwtTNtvYvnWobVMfFTQfeLKK HhuQ== X-Forwarded-Encrypted: i=1; AJvYcCWuen8UrH/UFHhSLvN/KxDEjBir5QGtZhapttha3ebh3Uaj6jiFGYYKxX7ksO1v20iMWOLrBjbTaNC/cCM=@vger.kernel.org X-Gm-Message-State: AOJu0Yyjp7ljvGOmr4YTbfUEtf/iBOuZ77RxXWxs6vWeK1ZTiibHfrdu R8WM1dIytT2diu2pYHXilZmzsNjUeUBgyMgQnSq6u6Gx9C+cK/pn8s2D X-Gm-Gg: ASbGnctlkl1CvBI3VncsV22jFRFXbpDXNs7TuYdpJT58yQNYpK5HgBbsGy4Fv02sTyv mUJ/U+HNTWWJ4OmxEgGQtH2up9u0fIhwybbhZmDDbqfh3OEyf6AatsyHe8iNzKSSNDKeeZbgT0A s3CqITyLSAx8JV/ekvM4XiljuuD5zYrkGSZM2H4ym8APFbhQBXEbmAZIyHPEV7mdKDhvVu8abZE 2xVpWaRomYeAxdFBkBjFQp47x72zGbEfqMCZqt+b9nD+2Li2D5MvSFmcJdcpqUW/Kun6Li9MQCe nVhbjgfwZbyb3U7ua2wYl4aIdmUigB3QVEV4dRZaRGcKrnW1cf0RwLiPUQ4Cu7wXVsaGOJ7ml+l yTimf9RBx7+rFI9PbfSYBzqFp0nHcV07G0GvampwPaJevAR3kdndrNKrzMzJOARXvgd87RtRpKg qi5XAZPMopg7PxfqQvdbKnyp0= X-Google-Smtp-Source: AGHT+IGotFF9hMuk2BHhjmZio101NvNZ9u1/TzQ6HKksgBv+fiil5SUdoiHKC9mTRtqP9GooBIX06A== X-Received: by 2002:a05:600c:3b87:b0:45f:2cd5:5086 with SMTP id 5b1f17b1804b1-45f2d345de3mr59942395e9.3.1758013288120; Tue, 16 Sep 2025 02:01:28 -0700 (PDT) Received: from xl-nested.c.googlers.com.com (42.16.79.34.bc.googleusercontent.com. [34.79.16.42]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-45e037186e5sm212975035e9.5.2025.09.16.02.01.27 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 16 Sep 2025 02:01:27 -0700 (PDT) From: Ethan Graham To: ethangraham@google.com, glider@google.com Cc: andreyknvl@gmail.com, andy@kernel.org, brauner@kernel.org, brendan.higgins@linux.dev, davem@davemloft.net, davidgow@google.com, dhowells@redhat.com, dvyukov@google.com, elver@google.com, herbert@gondor.apana.org.au, ignat@cloudflare.com, jack@suse.cz, jannh@google.com, johannes@sipsolutions.net, kasan-dev@googlegroups.com, kees@kernel.org, kunit-dev@googlegroups.com, linux-crypto@vger.kernel.org, linux-kernel@vger.kernel.org, linux-mm@kvack.org, lukas@wunner.de, rmoar@google.com, shuah@kernel.org, tarasmadan@google.com Subject: [PATCH v1 08/10] drivers/auxdisplay: add a KFuzzTest for parse_xy() Date: Tue, 16 Sep 2025 09:01:07 +0000 Message-ID: <20250916090109.91132-9-ethan.w.s.graham@gmail.com> X-Mailer: git-send-email 2.51.0.384.g4c02a37b29-goog In-Reply-To: <20250916090109.91132-1-ethan.w.s.graham@gmail.com> References: <20250916090109.91132-1-ethan.w.s.graham@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" From: Ethan Graham Add a KFuzzTest fuzzer for the parse_xy() function, located in a new file under /drivers/auxdisplay/tests. To validate the correctness and effectiveness of this KFuzzTest target, a bug was injected into parse_xy() like so: drivers/auxdisplay/charlcd.c:179 - s =3D p; + s =3D p + 1; Although a simple off-by-one bug, it requires a specific input sequence in order to trigger it, thus demonstrating the power of pairing KFuzzTest with a coverage-guided fuzzer like syzkaller. Signed-off-by: Ethan Graham --- drivers/auxdisplay/charlcd.c | 8 ++++++++ drivers/auxdisplay/tests/charlcd_kfuzz.c | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 drivers/auxdisplay/tests/charlcd_kfuzz.c diff --git a/drivers/auxdisplay/charlcd.c b/drivers/auxdisplay/charlcd.c index 09020bb8ad15..e079b5a9c93c 100644 --- a/drivers/auxdisplay/charlcd.c +++ b/drivers/auxdisplay/charlcd.c @@ -682,3 +682,11 @@ EXPORT_SYMBOL_GPL(charlcd_unregister); =20 MODULE_DESCRIPTION("Character LCD core support"); MODULE_LICENSE("GPL"); + +/* + * When CONFIG_KFUZZTEST is enabled, we include this _kfuzz.c file to ensu= re + * that KFuzzTest targets are built. + */ +#ifdef CONFIG_KFUZZTEST +#include "tests/charlcd_kfuzz.c" +#endif /* CONFIG_KFUZZTEST */ diff --git a/drivers/auxdisplay/tests/charlcd_kfuzz.c b/drivers/auxdisplay/= tests/charlcd_kfuzz.c new file mode 100644 index 000000000000..28ce7069c65c --- /dev/null +++ b/drivers/auxdisplay/tests/charlcd_kfuzz.c @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * charlcd KFuzzTest target + * + * Copyright 2025 Google LLC + */ +#include + +struct parse_xy_arg { + const char *s; +}; + +FUZZ_TEST(test_parse_xy, struct parse_xy_arg) +{ + unsigned long x, y; + + KFUZZTEST_EXPECT_NOT_NULL(parse_xy_arg, s); + KFUZZTEST_ANNOTATE_STRING(parse_xy_arg, s); + parse_xy(arg->s, &x, &y); +} --=20 2.51.0.384.g4c02a37b29-goog From nobody Thu Oct 2 14:21:31 2025 Received: from mail-wm1-f47.google.com (mail-wm1-f47.google.com [209.85.128.47]) (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 8E84229BD9C for ; Tue, 16 Sep 2025 09:01:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.47 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758013295; cv=none; b=a7qebWFYFf3JfHVax1fZyYsjM4rP64Qkgi1yjqznjWvtzgE2OO61IFt5r9ltRZ5xDijFuAEV2NtWNRaR6+mCTTuoRKsjE5ghi6j9Z91DdtPGOSppil+QrwerrkkWnQ6nVwahY2B5skNWbYC5KBh1Nyei5PLYp6XuBnDHUrHIntw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758013295; c=relaxed/simple; bh=b4STATL0Uju972ND8+fFwA9UTCoZh60N0y2XfSf6TNk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=mFSMQZ3oWfvbHS+7FWV0dcHpdPv1xtUogzJFbBM67VHK9fjuk0yr5UAh6xAeF4n1O+YWYrE4xSgnB6oV9E9kOfbCxxQC6y5LioXyHXVOpvEW7QG2mH0THoKmDebiojiHKwfiH5NUA1tsaq0DDEq7S9lw7/9U5XHeBWbmh1fAwdk= 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=U8IoFben; arc=none smtp.client-ip=209.85.128.47 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="U8IoFben" Received: by mail-wm1-f47.google.com with SMTP id 5b1f17b1804b1-45f2c9799a3so13442705e9.0 for ; Tue, 16 Sep 2025 02:01:31 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1758013290; x=1758618090; 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=54a6vrNAUJqXpuCeRFEgTKA2RP5sTalC0x0PuLBwSM4=; b=U8IoFbenmzVYutBdqet8d9Ceo8HAGhVzkmj3bYsgqUZbOsbCpQgR8SXa36jpm8kmcg vBeBRCjDmwKisWBHwVKafk58CX5FnBKWO2qggwnm5SsPoMdRWHtf8KKgyAKs53Q5HIUA lsLqiLezkGPLY/keh6uhm0MdYECBt3UVpFhHKmJz0FKrEXvuHK5zFOFMCPV1ZtOn96HY Zwu/twwxnaXEyH9gLqlKxRVLSzmACRCuNiLW7q68p9NF4lSB0fH5GHTc1I+1B+s/Mc8M zFDWMy9PHoMbr/zmfj9s5n3nTpDVqCU6rQQ39dLrL03cGWF1GOI5d9e2J2kEYC8qZ8o4 PHhw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1758013290; x=1758618090; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=54a6vrNAUJqXpuCeRFEgTKA2RP5sTalC0x0PuLBwSM4=; b=tVGetRKZGy5iKkGVcA3GToxamG2Npqmo7gogQFrvgvaekNOdwJhBaDMr5GFWif5s9b 6gufPJsUYkSf0FN1zWZX9CbQ8jBNHB0p2SOaHpg/ZFosEKlH8cTABRVI7AJVk81tjVzd O9u8yOIFIkL678Lbr9kVWtzwR1noMYbSRA+UlNxgfw0DN+LEKyxvVzzKX7Hy6N7kq2jR 8JQDVxb2+p+eWEzT9BP12exobxHvWxabIRMKix+5sdjB+AVRLbP8eBkaWN+2419tpXxF obNIZ0zZFgWi+n07lFYRjkpVQErynChmjiulwwci0e2NUV7Dc+vSPFkIy46xtl+pyclU NtOg== X-Forwarded-Encrypted: i=1; AJvYcCXTTNKIsQxiheIKRGMVxASVVAI1CMLFnbyUFP7QRs0cyFiXXTQqMDmfUQTF00A+w/iJftiOHvbB+YpTOQs=@vger.kernel.org X-Gm-Message-State: AOJu0YwI6L/eW8vyKYP0CsQS+wxwpZs/jznr7x4SWtt6S5/kkxEazsuS SbnF4+NYZPxnsBRqv+a4QtXSw+uc1HRj3iDeapegiuhLHTyTkG8cqUaf X-Gm-Gg: ASbGncsN27CNejiEmJK0p0450OfTs/DUvP80Jz0gDph4V7Rm2jCbOD5BWI3jOnA4N5D QLSOhdSVyWZVkr/LAj6cwdFo7q6i9Xg8KTISEwKk9ZsqpOO41ZopjeQtgcm5Y+tHzt+KC5UfIwj VB6H4bFy50COqDja7195ZgNEXrsEzqhp3KQvniVbPoA2zjps2NdBSAkSKJReTe1GGqkqh7ROc0F nNG16XvNaGX2dz5P8VDoU1VY2vzAMVfumBaZK787vq4OFOb8MMo4P4H1i1zMqpgdMkl4qjMVo2C 3zt5gE44+3zZb4Qff+N4myCAChWLphlyMkUlBZSsCh2fipYNlsKoQLU+8esq4zI5Pj5qXjDYfw9 H3e72rfE0vFMwPuF5sAauiJ/BJRHZtWoEQv23zYwPYxu4D5IX4qvZHOW3UvRr3aQ3U8eIAKblAb I257YbDhZ69W+X X-Google-Smtp-Source: AGHT+IGQ/nVkVKJ3q2nqGsYpu+DG+PZAA8Y4Ac4hVZt9d7cPYhzkDVimhmHreOcxPfvEMqJ6F549CQ== X-Received: by 2002:a05:600c:4446:b0:45d:f7e4:bf61 with SMTP id 5b1f17b1804b1-45f27ceb2f2mr101342565e9.4.1758013289608; Tue, 16 Sep 2025 02:01:29 -0700 (PDT) Received: from xl-nested.c.googlers.com.com (42.16.79.34.bc.googleusercontent.com. [34.79.16.42]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-45e037186e5sm212975035e9.5.2025.09.16.02.01.28 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 16 Sep 2025 02:01:28 -0700 (PDT) From: Ethan Graham To: ethangraham@google.com, glider@google.com Cc: andreyknvl@gmail.com, andy@kernel.org, brauner@kernel.org, brendan.higgins@linux.dev, davem@davemloft.net, davidgow@google.com, dhowells@redhat.com, dvyukov@google.com, elver@google.com, herbert@gondor.apana.org.au, ignat@cloudflare.com, jack@suse.cz, jannh@google.com, johannes@sipsolutions.net, kasan-dev@googlegroups.com, kees@kernel.org, kunit-dev@googlegroups.com, linux-crypto@vger.kernel.org, linux-kernel@vger.kernel.org, linux-mm@kvack.org, lukas@wunner.de, rmoar@google.com, shuah@kernel.org, tarasmadan@google.com Subject: [PATCH v1 09/10] fs/binfmt_script: add KFuzzTest target for load_script Date: Tue, 16 Sep 2025 09:01:08 +0000 Message-ID: <20250916090109.91132-10-ethan.w.s.graham@gmail.com> X-Mailer: git-send-email 2.51.0.384.g4c02a37b29-goog In-Reply-To: <20250916090109.91132-1-ethan.w.s.graham@gmail.com> References: <20250916090109.91132-1-ethan.w.s.graham@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" From: Ethan Graham Add a KFuzzTest target for the load_script function to serve as a real-world example of the framework's usage. The load_script function is responsible for parsing the shebang line (`#!`) of script files. This makes it an excellent candidate for KFuzzTest, as it involves parsing user-controlled data within the binary loading path, which is not directly exposed as a system call. The provided fuzz target in fs/tests/binfmt_script_kfuzz.c illustrates how to fuzz a function that requires more involved setup - here, we only let the fuzzer generate input for the `buf` field of struct linux_bprm, and manually set the other fields with sensible values inside of the FUZZ_TEST body. To demonstrate the effectiveness of the fuzz target, a buffer overflow bug was injected in the load_script function like so: - buf_end =3D bprm->buf + sizeof(bprm->buf) - 1; + buf_end =3D bprm->buf + sizeof(bprm->buf) + 1; Which was caught in around 40 seconds by syzkaller simultaneously fuzzing four other targets, a realistic use case where targets are continuously fuzzed. It also requires that the fuzzer be smart enough to generate an input starting with `#!`. While this bug is shallow, the fact that the bug is caught quickly and with minimal additional code can potentially be a source of confidence when modifying existing implementations or writing new functions. Signed-off-by: Ethan Graham --- fs/binfmt_script.c | 8 ++++++ fs/tests/binfmt_script_kfuzz.c | 51 ++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 fs/tests/binfmt_script_kfuzz.c diff --git a/fs/binfmt_script.c b/fs/binfmt_script.c index 637daf6e4d45..c09f224d6d7e 100644 --- a/fs/binfmt_script.c +++ b/fs/binfmt_script.c @@ -157,3 +157,11 @@ core_initcall(init_script_binfmt); module_exit(exit_script_binfmt); MODULE_DESCRIPTION("Kernel support for scripts starting with #!"); MODULE_LICENSE("GPL"); + +/* + * When CONFIG_KFUZZTEST is enabled, we include this _kfuzz.c file to ensu= re + * that KFuzzTest targets are built. + */ +#ifdef CONFIG_KFUZZTEST +#include "tests/binfmt_script_kfuzz.c" +#endif /* CONFIG_KFUZZTEST */ diff --git a/fs/tests/binfmt_script_kfuzz.c b/fs/tests/binfmt_script_kfuzz.c new file mode 100644 index 000000000000..9db2fb5a7f66 --- /dev/null +++ b/fs/tests/binfmt_script_kfuzz.c @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * binfmt_script loader KFuzzTest target + * + * Copyright 2025 Google LLC + */ +#include +#include +#include +#include + +struct load_script_arg { + char buf[BINPRM_BUF_SIZE]; +}; + +FUZZ_TEST(test_load_script, struct load_script_arg) +{ + struct linux_binprm bprm =3D {}; + char *arg_page; + + arg_page =3D (char *)get_zeroed_page(GFP_KERNEL); + if (!arg_page) + return; + + memcpy(bprm.buf, arg->buf, sizeof(bprm.buf)); + /* + * `load_script` calls remove_arg_zero, which expects argc !=3D 0. A + * static value of 1 is sufficient for fuzzing. + */ + bprm.argc =3D 1; + bprm.p =3D (unsigned long)arg_page + PAGE_SIZE; + bprm.filename =3D "fuzz_script"; + bprm.interp =3D bprm.filename; + + bprm.mm =3D mm_alloc(); + if (!bprm.mm) { + free_page((unsigned long)arg_page); + return; + } + + /* + * Call the target function. We expect it to fail and return an error + * (e.g., at open_exec), which is fine. The goal is to survive the + * initial parsing logic without crashing. + */ + load_script(&bprm); + + if (bprm.mm) + mmput(bprm.mm); + free_page((unsigned long)arg_page); +} --=20 2.51.0.384.g4c02a37b29-goog From nobody Thu Oct 2 14:21:31 2025 Received: from mail-wm1-f41.google.com (mail-wm1-f41.google.com [209.85.128.41]) (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 875A529B783 for ; Tue, 16 Sep 2025 09:01:32 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.41 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758013294; cv=none; b=X4c4e4IUQREk7BJGNoPXTdmL+k2cg/yotORhWAVocYNORSgqfcXYiA6rF8csNnm0sNLDkc7sbpNiCzY62LIcIE9ULTaT8A4vX/jH9tX6dLdSGzJbC4rO3sPhfnUXAr64fhvj5VAtr8hQq4G/L9Y8nuJfM2SSKKjy2VHKVz6LmCk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758013294; c=relaxed/simple; bh=6CI5ECYgOzpE43PbCnYe1/+C0o9IMjem75bN/KqMWZg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=G3/1GCglSRMAEMxm6vaEDUruiw1Rp+gNMc50fmJd7eqUhyRnpchDZn1lc4/nm+hzjYm7zh2blC5GD3si7aib6rpxFh/aXtxp8wh0Y9Px73981m1A2sszCwZS6aIQlHfm8qaR7rEzWQOGlH0iV5Qhdrb/GNTfZpJEIHb0JISEKCo= 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=DCOI4cbo; arc=none smtp.client-ip=209.85.128.41 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="DCOI4cbo" Received: by mail-wm1-f41.google.com with SMTP id 5b1f17b1804b1-45de56a042dso34126395e9.3 for ; Tue, 16 Sep 2025 02:01:32 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1758013291; x=1758618091; 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=Dn/O6EVQArdDsuUvojtCYkGjh1nRaP/fdtC5qzIrIQ0=; b=DCOI4cbo/4dqlsBIGjesWJZY9p5aqtD4gz7dlrnFKUlmt1u94XWYNzin65Akh6UTgk OxDaH+noI3H3IrIS9W0Ju5qG+ZUPYQtHvWJ7ADq9ikuOuven6K4bWwkNeWRdIeIWENsP uJhzvmKFLx0R3hNyd7Fh7lbOss24ki6IvKt8sleaIdxnpV0X1VqLtbKMwKK4t3T/8q1k HXFAMiQzkRBIiesslCyGmYIfRMcXH+H4PtqIgkEvLi7tfg6oIK90zs8AMIDnT2MiBniI 3BqqAAbsSEb6syCjhs7PdA5o0fgOufW6bdhTyFE3sPtCEPXJPsRojysUuzA+dEPsRDss EqPw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1758013291; x=1758618091; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=Dn/O6EVQArdDsuUvojtCYkGjh1nRaP/fdtC5qzIrIQ0=; b=RlW/w/sH7fbEHANi10Cvd/drM1wI1wbR4Kde99d2lG2jFZ2wBJEg3atJpAP8XGhkMI DHrCByRUFfg2Rm4/M8MIigGX6sKkLkn0T0UKXJb3dJmsjczKPrSHrwybpjKk7SoZwhCO YLOpAfGCMazCQATu8gw1ZJmRAVLIFagCwb5So0ECpAWsyLVmihfoCEFo3bDI3xjIh8j9 lP59yv1JOusvpeGg5AjpZQ+vRpUngOH09cDH4lBp79P+Gb7acFMqfkbMUwqWSMrs6sfv KpwS3Lwsy8mwoMdroklmaAN89svOpKzg1gCI5GZZPLN/yqFBpIHPe3mZWyCCYIuGHT2q PXWw== X-Forwarded-Encrypted: i=1; AJvYcCUIPrUNdliJK5/7IQg/dsnGbp1GFHeAVFedvLfmBwVm2i3gDyt3Zw0vc6r3It6+Wv0pu7HUsTgAbyl0K0w=@vger.kernel.org X-Gm-Message-State: AOJu0YyqAJHNkXKW9W1NEYSiKsTq0UCBzL9sWJfJic9tz+erUP0+ri50 PApaJRRS1WocDQYsw1eVditjfiBuc2lxCk0K5TjS3JBn5Ic+auOeh2RA X-Gm-Gg: ASbGncuuDl3rhp864fGj6XSOsZNNsFxhZ+vuDzbNthmIL3hFqrqnB0HM170fh2IXQSA QRhZPwQCNg5cA/dy9B8zyALH3oy6b2NXRhpiWJXB8Lww8yBfJ1noI6aL1RzSzvZBaEChiesYUPn 46HjuGjDRtOcxe+HNV4qEanKJcITYuoStlUp5wMPjh8eKWxLh10V8oeHpbZroG3Qw3vBs4lwuXd sVWxzZIFTxtIw7dVcKZz/2FQ9Zr5K1rbh16CemPPdpANOcNhyVpUhauD/HhlWe5FwzpRLx36YOe RgVujtMDPDRvfM5UIzkd728adv2xaF237zW4R0UYop3iupZ1/W6nardJ5Ozpejzef2OSgFoQeTT yzwCZIz//LVF5yI0DTMpGrHTEfFuoZTgAy+wNCr8g/16Tp8e6Ef1zWz8fdI81M37F38ZJuPNtvD uQ0tYieoUynlIX X-Google-Smtp-Source: AGHT+IHITqnIEWy8SR8VOuG/NaO5QPHcpXy3zWJ0Y1P3XtsNJD2TrXAHW1mpeJ/+27n2bIwqaq7a+A== X-Received: by 2002:a05:600c:a0b:b0:45b:74fc:d6ec with SMTP id 5b1f17b1804b1-45f211ca9dbmr161791435e9.8.1758013290736; Tue, 16 Sep 2025 02:01:30 -0700 (PDT) Received: from xl-nested.c.googlers.com.com (42.16.79.34.bc.googleusercontent.com. [34.79.16.42]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-45e037186e5sm212975035e9.5.2025.09.16.02.01.29 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 16 Sep 2025 02:01:29 -0700 (PDT) From: Ethan Graham To: ethangraham@google.com, glider@google.com Cc: andreyknvl@gmail.com, andy@kernel.org, brauner@kernel.org, brendan.higgins@linux.dev, davem@davemloft.net, davidgow@google.com, dhowells@redhat.com, dvyukov@google.com, elver@google.com, herbert@gondor.apana.org.au, ignat@cloudflare.com, jack@suse.cz, jannh@google.com, johannes@sipsolutions.net, kasan-dev@googlegroups.com, kees@kernel.org, kunit-dev@googlegroups.com, linux-crypto@vger.kernel.org, linux-kernel@vger.kernel.org, linux-mm@kvack.org, lukas@wunner.de, rmoar@google.com, shuah@kernel.org, tarasmadan@google.com Subject: [PATCH v1 10/10] MAINTAINERS: add maintainer information for KFuzzTest Date: Tue, 16 Sep 2025 09:01:09 +0000 Message-ID: <20250916090109.91132-11-ethan.w.s.graham@gmail.com> X-Mailer: git-send-email 2.51.0.384.g4c02a37b29-goog In-Reply-To: <20250916090109.91132-1-ethan.w.s.graham@gmail.com> References: <20250916090109.91132-1-ethan.w.s.graham@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" From: Ethan Graham Add myself as maintainer and Alexander Potapenko as reviewer for KFuzzTest. Acked-by: Alexander Potapenko --- MAINTAINERS | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 6dcfbd11efef..14972e3e9d6a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13641,6 +13641,14 @@ F: include/linux/kfifo.h F: lib/kfifo.c F: samples/kfifo/ =20 +KFUZZTEST +M: Ethan Graham +R: Alexander Potapenko +F: include/linux/kfuzztest.h +F: lib/kfuzztest/ +F: Documentation/dev-tools/kfuzztest.rst +F: tools/kfuzztest-bridge/ + KGDB / KDB /debug_core M: Jason Wessel M: Daniel Thompson --=20 2.51.0.384.g4c02a37b29-goog