Repository navigation
fuzz: fix the runner, fix 7 device-data bugs, fuzz more core commands - #4225
Merged
Merged
Conversation
The EOM descriptors follow each other at the descriptor size the device reports. An odd size places them at odd addresses, and the printers read their 16-bit fields through a misaligned struct pointer, which UBSan reports: nvme-print-json.c:2441:3: runtime error: member access within misaligned address for type 'struct nvme_eom_lane_desc', which requires 2 byte alignment Have eom_desc_iter_next() copy the fixed part of the descriptor, and hand the printable eye functions the eye data via eom_desc_iter_eye(). While at it, the stdout printer shows the Eye Data Length in CPU byte order too. Fixes: 25916bc ("nvme: Spport Phy Rx Eye Opening Measurement Log") Signed-off-by: Daniel Wagner <dwagner@suse.de>
get_phy_rx_eom_log() assigns the result of libnvme_realloc() to the buffer it grows, so when the device claims a log too large to allocate the header buffer is lost. The length is computed in 32 bits, too, and a large descriptor size times count wraps around. The Micron telemetry log reading has the same leak. Keep the old buffer until the new one exists, and compute the length in size_t. The fuzz-device target lets ASan fail a gigabyte allocation instead of aborting with any driver, not only with the options scripts/build.sh passes to libFuzzer: the regression input depends on it. Fixes: 25916bc ("nvme: Spport Phy Rx Eye Opening Measurement Log") Signed-off-by: Daniel Wagner <dwagner@suse.de>
With 8 MiB per run, a few bytes of fuzz input make the mock serve log pages of millions of zero-filled entries: reachability groups and associations, LBA status, or a hardware component log. nvme-cli prints them as 25 to 170 MB of text, which takes up to 10 seconds with the sanitizers, and libFuzzer reports such inputs as timeouts. None of them was a bug. Many small log pages find more than a few large ones: limit the data of a run to 1 MiB. Signed-off-by: Daniel Wagner <dwagner@suse.de>
A one hour fuzz run never finished: the timeout handler of one libFuzzer job deadlocked after it had printed the ALARM line, and the libFuzzer parent waits for all jobs. Run libFuzzer under timeout(1), which signals the whole process group, the jobs included, and kills it a minute later if that does not help. With -jobs equal to -workers, each worker runs a single job, and a job ends at its first finding: in that run, 10 of 16 workers stopped within the first five minutes. Queue more jobs than workers, the timeout ends the run. As libFuzzer no longer exits on its own, tell the findings by the inputs it saved in this run. A slow unit is no finding. Signed-off-by: Daniel Wagner <dwagner@suse.de>
The Get LBA Status data counts its descriptors in NLSD, but the buffer holds only as many as the MNDW the user asked for. Both printers walk NLSD descriptors and read past the buffer when the device reports more. The JSON printer also creates each descriptor as an array and then adds named members to it, which json-c rejects with an assertion: the JSON output aborted for every non-empty answer. And it compared the count in little endian. Print as many descriptors as the buffer holds, each as a JSON object. Fixes: 6372cd9 ("nvme-print-json: Add lba_status print function") Fixes: 2c413c9 ("Unify print routines") Signed-off-by: Daniel Wagner <dwagner@suse.de>
A Metadata Element Descriptor is 4 bytes and its value, so the next one can start at any offset, and the printers read the 16-bit length through a misaligned struct pointer. UBSan reports it, and the host metadata mock test fails in sanitizer builds: nvme-print-stdout.c:2552:13: runtime error: member access within misaligned address for type 'struct nvme_metadata_element_desc', which requires 2 byte alignment Walk the descriptors with nvme_host_metadata_element(), which copies the fixed part and keeps the bounds of the walk as they were. Fixes: 4499a26 ("nvme-print: Add Controller Metadata and Namespace Metadata") Signed-off-by: Daniel Wagner <dwagner@suse.de>
The printers of a vendor specific performance attribute dump ATTRL bytes of it, but ATTRL comes from the device and the attribute holds at most 4064: up to 64 KiB past the buffer are read. ATTRL is little endian too. Fixes: 5110bff ("nvme: add performance characteristic feature output") Signed-off-by: Daniel Wagner <dwagner@suse.de>
get_telemetry_log() allocates a buffer and passes it to the helpers, which replace it with the one they allocate: every telemetry log read leaks a block. And __get_telemetry_log_ctrl() does not free the header it reads before it gets the whole log. Let the helpers allocate, and free the header of the controller initiated log with a cleanup attribute. Fixes: 2f30d87 ("nvme: Fixed segmentation fault when getting host initiated telemetry") Fixes: 0040a90 ("nvme: Refactor get telemetry log page helper") Signed-off-by: Daniel Wagner <dwagner@suse.de>
The first byte of a fuzz-device input selects the command modulo the number of commands, so adding a command changes what most inputs do: the regression seeds would silently test other commands, and the corpus of earlier libFuzzer runs would be lost. Inputs which select no command are ignored instead. Every command still runs with fixed options, so whole branches can't be reached, the feature or namespace a command asks for. Add an argument byte after the variant, which a bit of the variant passes to the option a command names. Rewrite the seeds for the new format: the same command, variant and device answers as before. Signed-off-by: Daniel Wagner <dwagner@suse.de>
The device target covered the id and log commands well, but little else of the core commands and plugins. Add the commands which print what the device returns: the LBA format identify commands, the telemetry and boot partition logs (written to /dev/null), get-log, get-feature of the supported capabilities, get-lba-status, the registers and properties, all feat commands, fdp feature, the zns zone reports, sed discover, the rpmb, lm, directive, I/O management, NVMe-MI and security receive commands, fw commit and ns get-id. Pass the argument byte to the options which select what a command asks for: the feature, namespace, log, action, select or offset. For some commands already in the table too, like the feature ID of get-feature and the namespace of id ns. Add seeds for the new commands, and the inputs which found the bugs fixed before. Signed-off-by: Daniel Wagner <dwagner@suse.de>
The valgrind run of the fuzz suite aborted: a mutation made the OCP
persistent event log allocate and clear the 800 MiB its header claims,
and valgrind's shadow memory of it exceeded the address space limit of
the target, which valgrind shares:
Valgrind's memory management: out of memory:
memcheck:allocate new SecMap's request for 16384 bytes failed.
The mock answers a run with 1 MiB of data at most, so a buffer larger
than a few megabytes only ever holds zeros. Fail the libnvme_alloc(),
libnvme_realloc() and libnvme_alloc_huge() calls above 64 MiB: the
target defines them and forwards the others to libnvme, as the mock
does with open() and ioctl().
Signed-off-by: Daniel Wagner <dwagner@suse.de>
get_dispersed_ns_psub() bounds the participating NVM subsystem list to a 32 bit length, but grows its buffer to the header and that list, a 64 bit sum which libnvme_realloc() truncates to a 32 bit size_t: the buffer comes out a few bytes long, and the list is read past its end. The cross i386 build of the fuzz suite crashed on it. Fail a length which a size_t does not hold. Fixes: 6dba527 ("nvme: add dispersed-ns-participating-nss-log command") Signed-off-by: Daniel Wagner <dwagner@suse.de>
get_boot_part_log() passes the address of its buffer pointer to the printers, not the buffer: they show the bytes of the pointer as the log header, and the binary output writes the stack around it. An i386 sanitizer build of the fuzz suite reports it as a stack buffer overflow, on 64 bit the pointer is just large enough to hide it. The boot partition size from the log is little endian, too. Fixes: 079afcd ("nvme: add boot partition log support") Signed-off-by: Daniel Wagner <dwagner@suse.de>
qemu user mode, at least up to the 7.2 of Debian 12, does not emulate the SIOCETHTOOL ioctl and fails it with ENOSYS instead of the ENODEV or EOPNOTSUPP of the kernel. The net-util test then fails in a cross build which runs the tests with qemu. Check what matters: a query of a non-existent interface fails, and one of the loopback interface does not return a positive value. Signed-off-by: Daniel Wagner <dwagner@suse.de>
The cross files only worked in the Ubuntu CI containers: - They use a <triplet>-pkg-config, which Debian only has with the pkgconf package of the target architecture installed. i386 instead set PKG_CONFIG_LIBDIR when configuring, which a later reconfiguration by ninja lost, and the build then linked the libraries of the build machine. Use plain pkg-config with pkg_config_libdir set to the multiarch directory in the cross files. - The tests also run the binaries through binfmt_misc, and qemu finds no target libc unless it is installed for the target architecture in /. Point qemu at the libraries of the cross toolchain. The cross files have nothing Ubuntu specific left, drop it from their names. While at it, fix the path of the armhf ld, and make armhf the default target: there is no file for "arm". Document the packages a Debian host needs. With plain pkg-config, the s390x build finds the json-c of the CI container, which the <triplet>-pkg-config there did not, and runs the JSON based mock tests on big endian for the first time. Some of the micron and ocp ones fail, the plugins decode logs in host byte order. Build s390x without json-c, as before, until that is fixed. Signed-off-by: Daniel Wagner <dwagner@suse.de>
dwsuse
force-pushed
the
fuzz-device-coverage
branch
from
October 19, 2026 19:29
1f7d2e3 to
47676f1
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A one hour
scripts/build.sh fuzzrun of the device target neverfinished, and most of its findings were noise. This fixes the runner,
the bugs it found, and extends the device target to the core commands.
Runner
waited forever. libFuzzer now runs under timeout(1), which signals the
whole process group.
-jobsequal to-workersa worker stopped at its first finding(10 of 16 within five minutes). More jobs are queued than workers.
were valid logs of millions of zero-filled entries, printed as 25-170 MB
of text. Many small log pages find more than a few large ones.
claimed 800 MiB once, and valgrind ran out of memory clearing it.
Fixes (each with a regression input)
(and the same leak in the Micron telemetry log), 32-bit length overflow
aborted in json-c for every non-empty answer
host metadata mock test in sanitizer builds)
systems, and the list read past it (crashed the cross i386 build)
pointer, not the buffer, since the command was added
Device target
longer changes what the seeds and an existing corpus do. The seeds are
rewritten 1:1 for the new format.
--feature-id,--namespace-id,--log-id,--seland--action.featcommands,get-log,get-feature,get-lba-status, registers/properties,id *-lba-format, telemetryand boot partition logs,
fdp feature,zns report-zones/zone-mgmt-recv,sed discover,rpmb,lm,dir receive,io-mgmt/nvme-mi/security recv,fw commit,ns get-id.The first ten minute round with them found the last four bugs above.
Cross builds
pkg_config_libdir,QEMU_LD_PREFIXfor the tests run throughbinfmt_misc, renamed from
ubuntu-cross-*tocross-*. All fourtargets pass on Debian 12.
container's json-c, which the old wrapper never did, and some micron
and ocp JSON mock tests fail on big endian (logs decoded in host byte
order, e.g. through C bitfields). Left for a follow-up.
ethtool query, which qemu 7.2 does not emulate.
Note: a corpus from an earlier run is in the old input format.