VNX-822: Untrusted Pointer Dereference
A C/C++ pointer is built from data an attacker controls, so dereferencing it reads or writes an attacker-chosen address. Reported: (1) a cast to a pointer type (C-style, including function-pointer casts and (T *)(uintptr_t), or reinterpret_cast<T *>) applied directly to a text-to-integer conversion (strtoul, strtoull, strtol, strtoll, atoi, atol, atoll, std::stoul/stoull/stol/stoll/stoi); (2) the same cast, possibly through an integer cast ((T *)(uintptr_t)v) and including a dereferenced store (*(T *)v = x), applied to a variable assigned from such a conversion or filled by sscanf/fscanf/scanf (any conversion, such as %lx or SCNxPTR) in the ten lines above; (3) sscanf/fscanf/scanf reading a %p conversion; (4) a pointer read out of a buffer received from recv, recvfrom, read or fread in the 30 lines above through a pointer-to-pointer cast (*(T **)(buf + n)); (5) in a Linux ioctl handler (a function taking struct file * and unsigned long arg, the signature possibly spread over three lines), arg cast to a pointer type without __user, which dereferences a user-space address directly instead of copying it with copy_from_user. Not reported: integer conversions that stay integers, (void __user *)arg, an integer passed through a void * to pthread_create/thrd_create/g_idle_add-style callbacks, thread or callback arguments cast back to their context type, pointers to a validated table entry (&table[idx]), printf %p, and comments.
| Field | Value |
|---|---|
| Rule ID | VNX-822 |
| Severity | Medium |
| SARIF level | warning |
| Kind | sast |
| Languages | C, C++ |
| CWE | CWE-822 |
| CAPEC | CAPEC-66 |
| ATT&CK | T1190 |
| CWSS vector | CWSS:1.0/TI:M/AP:RU/AL:S/IC:N/FC:LT/RP:RU/RL:S/AV:L/AS:N/IN:A/SC:A/BI:M/DI:H/EX:M/EC:N/P:C |
| Tags | untrusted-pointer cwe-822 memory-safety |
| Source | internal/sast/rules/vnx-822.rego |
Overview
VNX-822 maps to CWE-822: Untrusted Pointer Dereference. It reports pointers made from numbers the caller supplies: parsed text, a packet, or a user-space ioctl argument.
What Gets Flagged
char *p = (char *)strtoul(argv[1], NULL, 16); /* FLAGGED */
void (*fn)(void) = (void (*)(void))strtoull(argv[2], 0, 0); /* FLAGGED */
unsigned long addr = strtoul(optarg, NULL, 0);
int *target = (int *)addr; /* FLAGGED: two-line form */
if (sscanf(line, "%p", &ptr) == 1) { ... } /* FLAGGED */
recv(sock, buf, sizeof(buf), 0);
struct node *next = *(struct node **)(buf + 8); /* FLAGGED */
static long demo_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
{
struct demo_req *r = (struct demo_req *)arg; /* FLAGGED: no __user / copy_from_user */
auto *obj = reinterpret_cast<Widget *>(std::stoull(req.param("handle"))); // FLAGGED
Not flagged: unsigned long n = strtoul(s, NULL, 10); used as a number, copy_from_user(&req, (void __user *)arg, sizeof(req)), a thread argument cast back to its context struct.
/* FLAGGED: address parsed with sscanf, then dereferenced */
unsigned long addr;
sscanf(cmdline, "peek %lx", &addr);
uint32_t value = *(volatile uint32_t *)addr;
Not flagged: pthread_create(&t, NULL, worker, (void *)(intptr_t)id), the integer-in-a-pointer idiom for a thread argument.
Remediation
- Never turn external input into an address. Pass handles as indexes into a table the program owns, and validate the index.
- In kernel code, copy user data with
copy_from_user/get_userthrough a__userpointer. - Parse network data field by field into local values instead of reading pointers out of the buffer.
References
Suppressing this rule
Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):
# nosec vnx-822
// nosec vnx-822
Or record an ignore rule: vulnetix ignore add --rule vnx-822 --reason "...". See Suppressing findings.