What are some of the best practices when implementing system call functionality for handling/avoiding "Time of check to time of use" (TOCTTOU) security issues?
One of the simplest ways to avoid TOCTTOU issues is for your system calls to perform the check at time of use (within the system call) instead of trusting a prior check. If you can gracefully fail a check upon use, the caller can instead rely upon exception handling to avoid a check race or a vulnerability. This methodology will help with both long-running and short-running scenarios.
To further assist with short-running scenarios, you could implement transactions for your security-related system calls. Of course, this would require being architected in early on, and would influence the design of the system call APIs, but it would provide concurrent consistency across the system.
In a kernel like anywhere else, the basic principle to avoid TOCTTOU bugs is to ensure that the time of check is the same as the time of use. That is, the check and the use must be part of the same atomic operation.
At the lowest level, kernel code can ensure that an operation is atomic by entering a critical section during which all interrupts are disabled. A critical section has a high cost (especially on a multiprocessor system), and so is only used when absolutely necessary and only for very short lengths of time.
Most system calls invoke a specific object, e.g. a file or device or process or other concept represented in the kernel by some data structure. A typical architecture associates a lock with each object. For example, a system call to open a device might do the following:
- take the lock associated with the process
- take the lock associated with the device
- match the calling process's privileges with the file permissions
- if the caller has the appropriate privilege, open the device
- release the device lock
- release the process lock
On a single-processor machine, many system calls can be atomic by the simple expedient of not relinquishing control from the kernel back to user land processes. Care must be taken that interrupts do not modify any object used by a system call; locks are useful for that. On multi-processor machines, locks must ensure that kernel code executing on different processors will not take conflicting actions.
One important piece of data that a system call manipulates is the arguments. On a multiprocessor machine, or on a single-processor machine with a preemptible kernel, the calling process may continue executing during the system call, so the kernel must take care to first copy the arguments into kernel memory, then validate its copy and use its copy.