Race conditions and synchronization issuesaccess(2) system callReal ID vs. Effective IDUsing access(2)A TOCTTOU attackProposed solution (fork)Proposed solution (k-race)Strategy of k-raceNew attackI/O delaysIncreasing probability of successConclusionRace conditions and synchronization issuesExploiting UNIXaccess(2) system callSUID programs run with privileges equal to the program owner (typically root or some privileged user) instead of callerMore specifically, SUID have effective ID equal to the owner, while real ID equal to callerSUID programs, when accessing files, may need to decide if the caller has privileges to the fileIt cannot rely on the basic UNIX mechanism for enforcing file permissions, as the privileges of the program’s owner are higher than that of the callerThe access(2) system call was introduced to address this issueSignature: int access(const char* path, int mode)Real ID vs. Effective IDaccess(2) determines privileges based on the real ID of the process. The mode can be any combination or read, write, execute, or existential test. It returns 0 (if success) or -1 in these events:[ENOTDIR] A component of the path prefix is not a directory.[ENAMETOOLONG] A component of a pathname exceeded {NAME_MAX} characters, or an entire path name exceeded {PATH_MAX} characters.[ENOENT] The named file does not exist.[ELOOP] Too many symbolic links were encountered in translating the pathname.[EROFS] Write access is requested for a file on a read-only file system.[ETXTBSY] Write access is requested for a pure procedure (shared text) file presently being executed.[EACCES] Permission bits of the file mode do not permit the requested access, or search permission is denied on a component of the path prefix. [EFAULT] Path points outside the process's allocated address space.[EIO] An I/O error occurred while reading from or writing to the file system.[EINVAL] An invalid value was specified for mode.Using access(2)In usage, a call is made to access(2) to check the caller’s privileges, and if it returns 0, a subsequent call to a file system operation such as open(2) is performed.Even if tightly coupled, the two operations are never atomic. In particular, both system calls take as inputs a path name, and must evaluate it in terms of a handle to a fileAs a result, it is proper to say that access(2) determines privileges with respect to a “snapshot” of the file system, while the subsequent call accesses a (possibly different) snapshot. Attackers can exploit this short time span to make improper changes to the file system. These changes will succeed because UNIX does not enforce write locks (e.g., modifications of the file system structure). Such enforcement is a necessary condition for security of the combined transaction.A TOCTTOU attackQuickTime™ and aTIFF (Uncompressed) decompressorare needed to see this picture.QuickTime™ and aTIFF (Uncompressed) decompressorare needed to see this picture.Proposed solution (fork)Dean and HU propose two portable solutions for the access(2) TOCTTOU vulnerability:Avoid access(2) altogether. If the SUID program needs to open a file that should be checked against caller’s privileges, do: 1. fork a new child process2. use setuid(2) system call to set the effective ID of child process equal to the caller (real ID) of parent process3. Let the forked process obtain a file descriptor4. Use standard UNIX IPC to pass the descriptor to parentThis solution enforces consistency, so it is secure. However, it is also slow.Proposed solution (k-race)Strategy of k-raceForce the attacker to win multiple racesEach call to access must be preceded by a switch of the symbolic link to some public file, while each call to open must resolve to the same file (attacker’s intended target)Additionally, after the first race, requests for file descriptors access the cache, which being very fast, results in attackers always losing the race.New attackCircumvent caching by creating many symbolic links, without re-using a link in any following raceForce victim to sleep on I/O, giving attacker execution time between races in which to modify symlinksDramatically increase the probability of success by:Using a symlink maze to dramatically increase the probability that the victim will sleep in each raceUse atime system call (which says when a directory has been traversed last) to predict when it is safe to change an intermediate symlink to point to system fileI/O delaysInstead of dir0, use dir/dir/…/dir/lnkVictim will sleep if at least one directory in the path is not in cacheUse chains of MAXPATHLEN (typically 1024-4096 characters) and use links to connect many chains in a maze (up to max symlinks supported in path)Victim must traverse C*N directories to resolve a name, where C is the number of symlinks and N the depth of each chainIn Linux, attack may be able to force traversal of 80,000 directories!Increasing probability of successThe attacker polls the directory structure to find out when the system updates access time on one of the symbolic links in the mazeAt that point, it is safe to switch that symlink to point to the system file resource.Experimental results: Success probability between 19% to 100% on k=100.Even randomizing when to make calls to access(2) or open(2) results in reasonable attack success probabilities K=100 already slower than fork-based solutionConclusionNeeded: Standardization of UNIX kernel capabilities to either:support temporary privilege dropsupport an O_RUID flag that makes system calls fail or succeed based on the real ID of the processNote: UNIX flavors often support better mechanisms than access(2), and therefore this call is in disuseHowever, code that uses such infrastructures is not
View Full Document