#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/