Steffen pretty much nailed it in his comment, but to expand on that:
TOCTOU isn't in any way specific to files or file systems; it's a vulnerability that is present any time you have:
- Untrusted input or state
- That gets validated before use
- But can then be modified such that
- The modified version is used without re-validation
Your scenario meets conditions #1 and #2, but (probably) not #3 or #4. The problem is that, when you upload a new version of "file.jpg" (your third step), that just creates an entirely new web request, which will be processed separately. The object in memory that uploadedFile
(or uploadedFile.contents
) points to isn't going to change during the execution of the function (or if it does, throw your server away and get one that's remotely sane). If the server supports parallel execution and/or preemptive multitasking, a new instance of the saveImage
function might start before the first one finishes, but the new instance's uploadedFile
will refer to the second upload, not the first one, and it will start again from the beginning (attempting to validate the second upload), while the first instance continues processing the initial upload. The first instance will complete successfully; the second will throw the error.
What would it take for this to be vulnerable to TOCTOU anyway? A minimum of two changes:
- The server would need to overwrite the memory buffer (or temporary file) used by the first upload when the second upload happens.
- The modified temporary file would need to be re-read (typically this happens if you close the file handle between steps, without caching the file contents within the function), or if it's a buffer, the
uploadedFile
variable (or at least its contents
member) would need to be re-read from memory that got overwritten in place (instead of e.g. a new allocation being created, in which case the old pointer would probably be cached).
Generally, the thread processing request X doesn't even have access (except in the strict sense of "shares an address space with") the parameters and local variables of the thread processing request Y, and vice versa. Deliberately overwriting the buffer of an incomplete request is unheard of. A server that did that wouldn't just be insecure, it wouldn't even work (after all, you have no way to control whether multiple browsers make requests to the same server at the same time, and indeed it's normal for that to happen; this is why real servers have a request queue and/or thread pool, and network listener sockets allow multiple waiting connections).
Temp files could have this problem, but it would need to both re-use the file name (usually temp files get random, unique names), and the saveImage
function would need to either allow another thread to modify the file while the handle is open and then re-read the file, or it would need to close the handle and re-open it. By default, on Linux the original file contents would still be there until the file is closed and reopened, and on Windows the other thread wouldn't be allowed to overwrite or delete the file at all until the thread running saveImage
closed it.
As a practical matter, even if it was vulnerable in theory, this would be tricky to exploit in practice from the same computer - network I/O is usually much slower than local processing - but you could do it if the validation step was very slow or the server was busy and stopped for a while after validation to do something else.
Also, just to be clear: "malicious executable contents" don't necessarily achieve anything. I can upload malware.exe (renamed to puppies.jpg) all day long if I want to; the server isn't going to execute it! Even if somebody downloads it again, they probably won't try to execute it, because they'll think it's an image (opens in another process that can display images), not an executable (used to create a new process that treats the file as machine instructions).
contents
is a static attribute ofuploadedFile
.