Repository navigation
[BUG] af_device_info: CUDA backend writes 257 bytes into buffers documented as 64 #3712
Description
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]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:
\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:
- Crash on exit with code 3221225477 arrayfire-rust#106 (crash on exit, code 3221225477 = 0xC0000005)
- [BUG] Getting STATUS_STACK_BUFFER_OVERRUN on any operations arrayfire-rust#285, [BUG] STATUS_STACK_BUFFER_OVERRUN from any operation arrayfire-rust#311 (STATUS_STACK_BUFFER_OVERRUN on trivial operations, both still undiagnosed)
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
- Using the latest available ArrayFire release
- GPU drivers are up to date
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.
Activity
[-][BUG] The CUDA backend's `devprop()` writes up to 257 bytes[/-][+][BUG] af_device_info: CUDA backend writes 257 bytes into buffers documented as 64[/+]