Project

General

Profile

Actions

Bug #9620

closed

getcwd() syscall has unbounded memory allocation

Added by Mike Gerdts over 3 years ago. Updated 10 months ago.

Status:
Closed
Priority:
Normal
Assignee:
Category:
kernel
Start date:
2018-06-21
Due date:
% Done:

100%

Estimated time:
Difficulty:
Bite-size
Tags:
Gerrit CR:

Description

If a file system allows you to create very deeply nested directories, getcwd() is written such that it would memory allocations up to the file system nesting limit. zfs seems to allow arbitrarily deep nesting, presumably subject to fs/pool space limits.

getcwd() is written such that if dogetcwd() returns ENAMETOOLONG, it will double the size of the buffer it allocates until it reaches the length that is long enough to store the path, presuming that the buffer size specified by the caller is large enough. Luckily, in the case that getcwd() anticipates, dogetcwd() returns ERANGE and instead of ENAMETOOLONG and the doubling of the buffer size never happens.

getcwd() should probably be changed to have a hard limit of MAXPATHLEN or some small multiple of it and do away with this reallocation loop before someone changes a function called by dogetcwd() such that it return ENAMETOOLONG and makes this exploitable.

Actions #1

Updated by Andy Fiddaman 11 months ago

It doesn't appear to be the case that it's broken - dogetcwd() does return ENAMETOOLONG if the buffer is insufficiently sized:

bloody# dtrace -n 'fbt::dogetcwd:return{trace(arg1)}' -c ./getcwd
dtrace: description 'fbt::dogetcwd:return' matched 1 probe
Err: 34 (Result too large)
dtrace: pid 4484 has exited
CPU     ID                    FUNCTION:NAME
  7  29661                  dogetcwd:return                78

% rg '(34|78)' /usr/include/sys/errno.h
84:#define    ERANGE  34 /* Math result not representable        */
140:#define   ENAMETOOLONG 78    /* path name is too long                */

However, it probably does make sense to put an upper bound on that loop.

Actions #2

Updated by Andy Fiddaman 11 months ago

However, the getcwd syscall does not work for very long paths, eventually returning ERANGE.

bloody# echo ${#PWD}
32782
bloody# dtrace -n 'fbt::getcwd:entry,fbt::dogetcwd:return{self->t++}' \
    -n 'fbt::dogetcwd:entry,fbt::getcwd:return{self->t--}' \
    -n 'fbt::kmem_alloc:entry/self->t/{trace(arg0)}' \
    -n 'fbt::*cwd*:return{trace(arg1)}' -c ~/getcwd
dtrace: description 'fbt::getcwd:entry,fbt::dogetcwd:return' matched 3 probes
dtrace: description 'fbt::dogetcwd:entry,fbt::getcwd:return' matched 3 probes
dtrace: description 'fbt::kmem_alloc:entry' matched 1 probe
dtrace: description 'fbt::*cwd*:return' matched 4 probes
Err: 34 (Result too large)
dtrace: pid 5164 has exited
CPU     ID                    FUNCTION:NAME
  8  28295                 kmem_alloc:entry              1024
  8  29661                  dogetcwd:return                78
  8  28295                 kmem_alloc:entry              2048
  8  29661                  dogetcwd:return                78
  8  28295                 kmem_alloc:entry              4096
  8  29661                  dogetcwd:return                78
  8  28295                 kmem_alloc:entry              8192
  8  29661                  dogetcwd:return                78
  8  28295                 kmem_alloc:entry             16384
  8  29661                  dogetcwd:return                78
  8  28295                 kmem_alloc:entry             32768
  8  29661                  dogetcwd:return                34
  8  30537                    getcwd:return                34

Actions #3

Updated by Andy Fiddaman 11 months ago

  • Subject changed from getcwd() syscall looks to have unbounded memory allocation, but thankfully is broken to getcwd() syscall has unbounded memory allocation
  • Category set to kernel
  • Status changed from New to In Progress
  • Assignee set to Andy Fiddaman
  • Difficulty changed from Medium to Bite-size
  • Tags deleted (needs-triage)
Actions #4

Updated by Andy Fiddaman 11 months ago

Aside from the potential unbounded memory usage, the underlying problem seems to be that
dirtopath() sometimes returns ERANGE and sometimes ENAMETOOLONG, depending on whether vnode path for a particular path component is cached or not, and the getcwd() syscall only retries with a bigger buffer on ERANGE.

Testing from a shell that has a working directory with a long path:

bloody# echo ${#PWD}
32782

and having patched the getcwd() function to retry on both ERANGE and ENAMETOOLONG, the system call works.

buf = getcwd(NULL, 33000);
printf("Got: %d\n", strlen(buf));
bloody# dtrace -n 'fbt::getcwd:entry,fbt::dogetcwd:return{self->t++}' \
    -n 'fbt::dogetcwd:entry,fbt::getcwd:return{self->t--}' \
    -n 'fbt::kmem_alloc:entry/self->t/{trace(arg0)}' \
    -n 'fbt::*cwd*:return{trace(arg1)}' -c ~/getcwd
dtrace: description 'fbt::getcwd:entry,fbt::dogetcwd:return' matched 3 probes
dtrace: description 'fbt::dogetcwd:entry,fbt::getcwd:return' matched 3 probes
dtrace: description 'fbt::kmem_alloc:entry' matched 1 probe
dtrace: description 'fbt::*cwd*:return' matched 3 probes
Got: 32782
dtrace: pid 680 has exited
CPU     ID                    FUNCTION:NAME
  3  17046                 kmem_alloc:entry              1024
  3  18404                  dogetcwd:return                78
  3  17046                 kmem_alloc:entry              2048
  3  18404                  dogetcwd:return                78
  3  17046                 kmem_alloc:entry              4096
  3  18404                  dogetcwd:return                78
  3  17046                 kmem_alloc:entry              8192
  3  18404                  dogetcwd:return                78
  3  17046                 kmem_alloc:entry             16384
  3  18404                  dogetcwd:return                78            <- ENAMETOOLONG
  3  17046                 kmem_alloc:entry             32768
  3  18404                  dogetcwd:return                34            <- ERANGE
  3  17046                 kmem_alloc:entry             33000
  3  18404                  dogetcwd:return                 0             <- SUCCESS
  3  19270                    getcwd:return                 0
Actions #5

Updated by Electric Monk 11 months ago

  • Gerrit CR set to 1299
Actions #6

Updated by Andy Fiddaman 11 months ago

Some more testing notes:

With a path that is bigger than kmem_max_cached.
This is in conjunction with #13586, libc keeps trying bigger and bigger buffers up to its limit, and eventually fails with ERANGE.

bloody# echo ${#PWD}
140023

bloody# ~/getcwd/syscall.d -c "$HOME/getcwd/getcwd" 
dtrace: script '/data/omnios-build/getcwd/syscall.d' matched 10 probes
Calling getcwd(NULL, 0)
Err: 34 (Result too large)
dtrace: pid 917 has exited
CPU     ID                    FUNCTION:NAME
 13  17047                 kmem_alloc:entry              1024
 13  18405                  dogetcwd:return                78
 13  19271                    getcwd:return                34

 13  17047                 kmem_alloc:entry              1024
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry              2048
 13  18405                  dogetcwd:return                78
 13  19271                    getcwd:return                34

 13  17047                 kmem_alloc:entry              1024
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry              2048
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry              4096
 13  18405                  dogetcwd:return                78
 13  19271                    getcwd:return                34

 13  17047                 kmem_alloc:entry              1024
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry              2048
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry              4096
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry              8192
 13  18405                  dogetcwd:return                78
 13  19271                    getcwd:return                34

 13  17047                 kmem_alloc:entry              1024
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry              2048
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry              4096
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry              8192
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry             16384
 13  18405                  dogetcwd:return                78
 13  19271                    getcwd:return                34

 13  17047                 kmem_alloc:entry              1024
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry              2048
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry              4096
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry              8192
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry             16384
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry             32768
 13  18405                  dogetcwd:return                78
 13  19271                    getcwd:return                34

 13  17047                 kmem_alloc:entry              1024
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry              2048
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry              4096
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry              8192
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry             16384
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry             32768
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry             65536
 13  18405                  dogetcwd:return                78
 13  19271                    getcwd:return                34

 13  17047                 kmem_alloc:entry              1024
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry              2048
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry              4096
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry              8192
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry             16384
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry             32768
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry             65536
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry            131072
 13  18405                  dogetcwd:return                78
 13  19271                    getcwd:return                34

Testing without #13586, calling getcwd(NULL, 150000)

bloody# echo ${#PWD}
140023
bloody# ~/getcwd/syscall.d -c "$HOME/getcwd/getcwd 150000" 
dtrace: script '/data/omnios-build/getcwd/syscall.d' matched 10 probes
Calling getcwd(NULL, 150000)
Err: 34 (Result too large)
dtrace: pid 924 has exited
CPU     ID                    FUNCTION:NAME
 13  17047                 kmem_alloc:entry              1024
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry              2048
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry              4096
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry              8192
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry             16384
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry             32768
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry             65536
 13  18405                  dogetcwd:return                78
 13  17047                 kmem_alloc:entry            131072
 13  18405                  dogetcwd:return                78
 13  19271                    getcwd:return                34
Actions #7

Updated by Andy Fiddaman 11 months ago

and with a user supplied buffer that is too small:

bloody# ~/getcwd/syscall.d -c "$HOME/getcwd/getcwd 20000" 
dtrace: script '/data/omnios-build/getcwd/syscall.d' matched 10 probes
Calling getcwd(NULL, 20000)
Err: 34 (Result too large)
dtrace: pid 524 has exited
CPU     ID                    FUNCTION:NAME
 12  17047                 kmem_alloc:entry              1024
 12  18405                  dogetcwd:return                78
 12  17047                 kmem_alloc:entry              2048
 12  18405                  dogetcwd:return                78
 12  17047                 kmem_alloc:entry              4096
 12  18405                  dogetcwd:return                78
 12  17047                 kmem_alloc:entry              8192
 12  18405                  dogetcwd:return                78
 12  17047                 kmem_alloc:entry             16384
 12  18405                  dogetcwd:return                78
 12  17047                 kmem_alloc:entry             20000
 12  18405                  dogetcwd:return                78
 12  19271                    getcwd:return                34
Actions #8

Updated by Electric Monk 10 months ago

  • Status changed from In Progress to Closed
  • % Done changed from 0 to 100

git commit 7eb8c88abb70697edf48045434d2c18bb82ad2e7

commit  7eb8c88abb70697edf48045434d2c18bb82ad2e7
Author: Andy Fiddaman <omnios@citrus-it.co.uk>
Date:   2021-03-16T15:52:24.000Z

    9620 getcwd() syscall has unbounded memory allocation
    Reviewed by: Jason King <jason.king@joyent.com>
    Reviewed by: Dan McDonald <danmcd@joyent.com>
    Reviewed by: Gordon Ross <gordon.w.ross@gmail.com>
    Approved by: Robert Mustacchi <rm@fingolfin.org>

Actions

Also available in: Atom PDF