#Note/Permanent #Security/Vulnerabilities/RaceConditions #Programming/Concurrency
A Time-of-Check to Time-of-Use (TOCTOU) vulnerability occurs when a program checks the state of a resource (permissions, ownership, file type, or existence) but uses that resource at a later time, creating a time window where an attacker can modify the resource's state between the check and use operations. This class of race condition vulnerability can lead to privilege escalation, unauthorized access, and arbitrary file operations.
## The Vulnerability Mechanism
TOCTOU exploits follow this pattern:
1. **Check**: Program validates permissions or state of a resource
2. **Time Window**: Brief period where resource state can change
3. **Use**: Program operates on the resource assuming the check still holds
The attacker exploits this time window to modify the resource (via symlinks, file replacement, memory pointer manipulation, etc.), causing the program to operate on an unintended target. When the vulnerable program runs with elevated privileges, this leads to privilege escalation or unauthorized access.
## Simple Vulnerable Patterns
### Pattern 1: Classic File Access Race
```c
if (access(argv[1], R_OK) == 0) { // check
seteuid(0);
// time window
FILE *f = fopen(argv[1], "r"); // use
copy_to_backup_tape(f);
fclose(f);
}
```
### Pattern 2: File Existence Check Race
```c
sprintf(log_entry, "# User %s ran: %s\n", username, user_command);
if (stat("/tmp/daily_activity.log", &st) == -1) { // check
// time window
int fd = open("/tmp/daily_activity.log", O_CREAT | O_WRONLY); // use
write(fd, log_entry, strlen(log_entry));
close(fd);
}
```
### Pattern 3: Symlink Detection Race
```c
if (lstat("/var/log/app.log", &st) == 0 && S_ISLNK(st.st_mode)) { // check
unlink("/var/log/app.log");
}
// time window
int fd = open("/var/log/app.log", O_CREAT | O_WRONLY); // use
write(fd, log_line, strlen(log_line));
close(fd);
```
## Real-World CVE Examples
### CVE-2024-30088: Windows Kernel Security Attributes TOCTOU
```cpp
__kernel_entry NTSYSCALLAPI NTSTATUS NtQueryInformationToken(
[in] HANDLE TokenHandle,
[in] TOKEN_INFORMATION_CLASS TokenInformationClass,
[out] PVOID TokenInformation, // usermode buffer
[in] ULONG TokenInformationLength,
[out] PULONG ReturnLength
);
NtQueryInformationToken( ... ) {
...
case TokenAccessInformation: // case 22
...
if ( v5 < TokenAccessInformationBufferSize )
goto LABEL_58;
SepCopyTokenAccessInformation(
v30,
userBuffer, // TokenInformation field
...
}
__int64 __fastcall SepCopyTokenAccessInformation(
_TOKEN *Token,
_TOKEN_ACCESS_INFORMATION *userBuffer, // usermode buffer
... ) {
...
AuthzBasepQueryInternalSecurityAttributesToken(
Token->pSecurityAttributes,
userBufferSecurityAttributes, // usermode buffer
userBufferEnd - userBufferSecurityAttributes,
&v38
);
...
}
__int64 __fastcall AuthzBasepQueryInternalSecurityAttributesToken(
_AUTHZBASEP_SECURITY_ATTRIBUTES_INFORMATION *tokenSecurityAttributes,
_DWORD *userBufferSecurityAttributes,
unsigned int securityAttributesSize,
unsigned int *a4) {
...
result = AuthzBasepCopyoutInternalSecurityAttributes(
tokenSecurityAttributes,
userBufferSecurityAttributes, // usermode buffer
securityAttributesSize);
...
}
__int64 __fastcall AuthzBasepCopyoutInternalSecurityAttributes(
_AUTHZBASEP_SECURITY_ATTRIBUTES_INFORMATION *tokenSecAttr,
int *userBufferSecurityAttributes, // usermode buffer
unsigned int a3) {
...
// usermode buffer
ADJ(offsetToUserBuffer)->unicodeString.MaximumLength = maxLength;
ADJ(offsetToUserBuffer)->unicodeString.Length = 0;
// pBuffer is created by the kernel, trusted when created
ADJ(offsetToUserBuffer)->unicodeString.Buffer = pBuffer;
// window
RtlCopyUnicodeString(&ADJ(offsetToUserBuffer)->unicodeString, (SA_Entry + 0x20)); // use
v9 = AuthzBasepCopyoutInternalSecurityAttributeValues(SA_Entry, offsetToUserBuffer - 104, v18, v6 - v18, &v20);
if ( (v9 & 0x80000000) != 0 )
goto LABEL_18;
...
}
```
Where `pBuffer` is a variable created by the kernel and trusted. The `Buffer` field is part of the user-provided memory. It's part of the procedure to construct the `TOKEN_ACCESS_INFORMATION` object within the user-provided memory. During the window, a racing thread can modify the `Buffer` field to point to arbitrary memory, causing `RtlCopyUnicodeString` to write fixed data to that address with kernel privileges to achieve privilege escalation to SYSTEM. See the [blog](https://starlabs.sg/blog/2025/07-fooling-the-sandbox-a-chrome-atic-escape/) by Vincent Yeo and PoC by [tykawaii98](https://github.com/tykawaii98).
## Reference
- [CWE-367: Time-of-Check Time-of-Use (TOCTOU) Race Condition](https://cwe.mitre.org/data/definitions/367.html)
- https://github.com/tykawaii98/CVE-2024-30088/blob/main/poc/main.cpp
- https://starlabs.sg/blog/2025/07-fooling-the-sandbox-a-chrome-atic-escape/