The CUDA backend's devprop() writes up to 257 bytes into d_name, but af_device_info's documented contract states a recommended minimum size of 64. Callers that follow the documentation get 64–256 bytes of memory past their buffer overwritten.
The CPU and OpenCL backends honour the documented size; only CUDA does not.
Description
src/backend/cuda/platform.cpp:290-307:
snprintf(d_name, 256, "%s", dev.name); // up to 256 bytes
snprintf(d_platform, 10, "CUDA");
snprintf(d_toolkit, 64, "v%s", cudaRuntime.c_str());
snprintf(d_compute, 10, "%d.%d", dev.major, dev.minor);
// Sanitize input
for (int i = 0; i < 256; i++) {
if (d_name[i] == ' ') { // reads d_name[0..256]
if (d_name[i + 1] == 0 || d_name[i + 1] == ' ') {
d_name[i] = 0; // writes d_name[0..255]
} else {
d_name[i] = '_';
}
}
}
Two separate problems:
snprintf(d_name, 256, ...) may write up to 256 bytes into a buffer the docs say can be 64.
- The sanitize loop is unconditional. It does not stop at the NUL terminator, so it scans all 257 bytes (
d_name[i + 1] at i == 255 reads index 256) regardless of how short the device name is, and writes 0x00 or '_' at every index where it happens to read a 0x20 byte. So the overflow is not conditional on having a long GPU name — it fires on every call.
The documented contract, docs/details/device.dox:10-16:
\param d_name pointer to a user-allocated char array. Recommended minimum size is 64.
\param d_platform pointer to a user-allocated char array. Recommended minimum size is 10.
\param d_toolkit pointer to a user-allocated char array. Recommended minimum size is 64.
\param d_compute pointer to a user-allocated char array. Recommended minimum size is 10.
The signature takes no length parameters:
AFAPI af_err af_device_info(char* d_name, char* d_platform, char *d_toolkit, char* d_compute);
so the documented minimum is the only size contract available to a caller.
Any caller sizing d_name per the documentation has up to 193 bytes of adjacent memory clobbered. For a stack-allocated buffer that spans the other three output buffers, spilled registers, the /GS cookie and the return address — producing STATUS_ACCESS_VIOLATION (0xC0000005) or STATUS_STACK_BUFFER_OVERRUN (0xC0000409) on Windows.
Because whether it faults depends on which adjacent bytes happen to contain 0x20, the symptom is intermittent, which makes it hard to attribute.
This is a plausible root cause for several long-standing crash reports in the Rust bindings, which allocate exactly the documented 64/10/64/10 and call af_device_info in the first example program:
Reproducible Code and/or Steps
see above
Suggested fix
Bound the writes to the documented sizes:
snprintf(d_name, 64, "%s", dev.name);
...
for (int i = 0; i < 63 && d_name[i] != '\0'; i++) {
Or at least update the documented minimum buffer size to 256.
System Information
code correctness issue, not system-specific
Checklist
Disclaimer
Found using Claude Opus 5. I prompted it to look for potential sources for spurious 0xC0000005 errors in the Rust wrapper (sometimes triggered by just enumerating devices). I looked through the mentioned code parts manually and it seems like this finding is correct.
The CUDA backend's
devprop()writes up to 257 bytes intod_name, butaf_device_info's documented contract states a recommended minimum size of 64. Callers that follow the documentation get 64–256 bytes of memory past their buffer overwritten.The CPU and OpenCL backends honour the documented size; only CUDA does not.
Description
src/backend/cuda/platform.cpp:290-307:Two separate problems:
snprintf(d_name, 256, ...)may write up to 256 bytes into a buffer the docs say can be 64.d_name[i + 1]ati == 255reads index 256) regardless of how short the device name is, and writes0x00or'_'at every index where it happens to read a0x20byte. So the overflow is not conditional on having a long GPU name — it fires on every call.The documented contract,
docs/details/device.dox:10-16:The signature takes no length parameters:
so the documented minimum is the only size contract available to a caller.
Any caller sizing
d_nameper the documentation has up to 193 bytes of adjacent memory clobbered. For a stack-allocated buffer that spans the other three output buffers, spilled registers, the/GScookie and the return address — producingSTATUS_ACCESS_VIOLATION(0xC0000005) orSTATUS_STACK_BUFFER_OVERRUN(0xC0000409) on Windows.Because whether it faults depends on which adjacent bytes happen to contain
0x20, the symptom is intermittent, which makes it hard to attribute.This is a plausible root cause for several long-standing crash reports in the Rust bindings, which allocate exactly the documented 64/10/64/10 and call
af_device_infoin the first example program:Reproducible Code and/or Steps
see above
Suggested fix
Bound the writes to the documented sizes:
Or at least update the documented minimum buffer size to 256.
System Information
code correctness issue, not system-specific
Checklist
Disclaimer
Found using Claude Opus 5. I prompted it to look for potential sources for spurious
0xC0000005errors in the Rust wrapper (sometimes triggered by just enumerating devices). I looked through the mentioned code parts manually and it seems like this finding is correct.