detailed description is in the gentoo forum. short summary. emerge tar with sandbox feature says that getcwd doesn't work, while without sandbox feature getcwd works. diff between the log files: bigmichi1 ~ # diff os ws 223c223 < checking whether getcwd handles long file names properly... yes --- > checking whether getcwd handles long file names properly... no 340d339 < mv alloca.h-t alloca.h 341a341 > mv alloca.h-t alloca.h 346d345 < mv sysexits.h-t sysexits.h 347a347 > mv sysexits.h-t sysexits.h 351d350 < echo "# define DEFAULT_RMT_COMMAND \"/usr/sbin/`echo \"rmt\" | sed 's,^.*/,,;s,x,x,'`\"" >> localedir.h 353a353 > echo "# define DEFAULT_RMT_COMMAND \"/usr/sbin/`echo \"rmt\" | sed 's,^.*/,,;s,x,x,'`\"" >> localedir.h 354a355,356 > if i686-pc-linux-gnu-gcc -DHAVE_CONFIG_H -DLIBDIR=\"/usr/lib\" -I. -I. -I.. -march=pentium3 -O2 -pipe -fomit-frame-pointer -fforce-addr -MT getcwd.o -MD -MP -MF ".deps/getcwd.Tpo" -c -o getcwd.o getcwd.c; \ > then mv -f ".deps/getcwd.Tpo" ".deps/getcwd.Po"; else rm -f ".deps/getcwd.Tpo"; exit 1; fi 356d357 < mv t-charset.alias charset.alias 358d358 < sed -e '/^#/d' -e 's/@''PACKAGE''@/tar/g' ref-del.sin > t-ref-del.sed 359a360,361 > mv t-charset.alias charset.alias > sed -e '/^#/d' -e 's/@''PACKAGE''@/tar/g' ref-del.sin > t-ref-del.sed 438c440 < ar cru libtar.a prepargs.o rtapelib.o allocsa.o argmatch.o argp-ba.o argp-eexst.o argp-fmtstream.o argp-fs-xinl.o argp-help.o argp-parse.o argp-pv.o argp-pvh.o argp-xinl.o backupfile.o dirname.o basename.o stripslash.o exclude.o exitfail.o full-write.o getdate.o gettime.o hash.o human.o localcharset.o modechange.o quote.o quotearg.o safe-read.o safe-write.o save-cwd.o savedir.o xmalloc.o xalloc-die.o xgetcwd.o xstrtol.o xstrtoul.o xstrtoumax.o getopt.o getopt1.o --- > ar cru libtar.a prepargs.o rtapelib.o allocsa.o argmatch.o argp-ba.o argp-eexst.o argp-fmtstream.o argp-fs-xinl.o argp-help.o argp-parse.o argp-pv.o argp-pvh.o argp-xinl.o backupfile.o dirname.o basename.o stripslash.o exclude.o exitfail.o full-write.o getdate.o gettime.o hash.o human.o localcharset.o modechange.o quote.o quotearg.o safe-read.o safe-write.o save-cwd.o savedir.o xmalloc.o xalloc-die.o xgetcwd.o xstrtol.o xstrtoul.o xstrtoumax.o getopt.o getopt1.o getcwd.o Reproducible: Always Steps to Reproduce: 1. 2. 3. Portage 2.0.51.22-r1 (default-linux/x86/2005.0, gcc-3.4.3-20050110, glibc-2.3.5-r0, 2.6.12-rc5-Gentoo-2005.0 i686) ================================================================= System uname: 2.6.12-rc5-Gentoo-2005.0 i686 Pentium III (Coppermine) Gentoo Base System version 1.6.12 ccache version 2.4 [enabled] dev-lang/python: 2.3.5 sys-apps/sandbox: 1.2.8 sys-devel/autoconf: 2.13, 2.59-r6 sys-devel/automake: 1.4_p6, 1.5, 1.6.3, 1.7.9-r1, 1.8.5-r3, 1.9.5 sys-devel/binutils: 2.16-r1 sys-devel/libtool: 1.5.18 virtual/os-headers: 2.6.11-r1 ACCEPT_KEYWORDS="x86 ~x86" AUTOCLEAN="yes" CBUILD="i686-pc-linux-gnu" CFLAGS="-march=pentium3 -O2 -pipe -fomit-frame-pointer -fforce-addr" CHOST="i686-pc-linux-gnu" CONFIG_PROTECT="/etc /usr/kde/2/share/config /usr/kde/3/share/config /usr/lib/X11/xkb /usr/share/config /var/bind /var/qmail/control" CONFIG_PROTECT_MASK="/etc/gconf /etc/terminfo /etc/texmf/web2c /etc/env.d" CXXFLAGS="-march=pentium3 -O2 -pipe -fomit-frame-pointer -fforce-addr" DISTDIR="/usr/portage/distfiles" FEATURES="autoconfig ccache distlocks maketest sandbox sfperms strict test" GENTOO_MIRRORS="http://distfiles.gentoo.org http://distro.ibiblio.org/pub/Linux/distributions/gentoo" MAKEOPTS="-j3" PKGDIR="/usr/portage/packages" PORTAGE_TMPDIR="/var/tmp" PORTDIR="/usr/portage" PORTDIR_OVERLAY="/usr/local/portage" SYNC="rsync://rsync.gentoo.org/gentoo-portage" USE="x86 X X509 Xaw3d aalib acl activefilter alsa aotuv apache2 apm avi bash-completion bcmath berkdb bitmap-fonts bzip2 bzlib calendar caps cpdflib crypt ctype cups curl curlwrappers dba dga dio directfb djbfft doc ecc emboss encode erandom esd exif extensions extraengine fbcon flac flatfile font-server foomaticdb fortran ftp gcj gd gdbm geometry ggi gif gmp gpm gtk gtk2 guile iconv idea idled idn imap imlib inifile innodb insecure-drivers ipv6 java jce jpeg justify libcaca libclamav libg++ libwww mad memlimit mhash mikmod mime mmx motif mp3 mpeg mpm-prefork mysql ncurses neXt netboot nis nls no_wxgtk1 nonfsv4 nptl nptlonly oav objc ogg oggvorbis opengl oss pam pam_chroot pam_timestamp pcntl pcre pdflib perl perlsuid php physfs png posix pwdb python quicktime quotas readline rrdtool samba sample sasl sdl sendfile session sftplogging shaper simplexml slang sndfile soap sockets softquota speex spell spl sqlite srp sse ssl svga symlink sysfs sysvipc tcltk tcpd tetex tidy tiff tokenizer truetype truetype-fonts type1-fonts ucs2 unicode urandom utf8 vorbis wddx wxwindows xml xml2 xmlrpc xmms xpm xprint xsl xv zlib video_cards_ati128 userland_GNU kernel_linux elibc_glibc" Unset: ASFLAGS, CTARGET, LANG, LC_ALL, LDFLAGS, LINGUAS
we cant really guess what package you're playing with if you dont tell us ...
Ok, this is a valid issue (see also bug #21766). I will try to explain shortly. Basically the issue is with applications like tar or coreutils that checks if getcwd will handle too long filenames properly (return ENAMETOOLONG, etc). How this function does it (source at bottom) in very simple terms, is create a path longer than PATH_MAX via a 'mkdir confdir3; chdir confdir3' loop, and then check that getcwd fails with propper error code. This however throws a spanner in sandbox code, as we want to check every path (in this case the mkdir() call) if the caller have write access. We can only do this if we can get the full path to the directory being created (have to compare against write list). The problem now comes in because lstat, getcwd (even generic implementations that do not use the syscall) do not handle larger than PATH_MAX, and we need them to canonicalize a non-absolute path like in the case of 'mkdir confdir3'. The path thus gets too deep for our canonicalize function to handle, and it deny's access to the test program if it tries to mkdir() again, which causes it to fail. ----- #include <errno.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <limits.h> #include <sys/stat.h> #include <sys/types.h> #if HAVE_FCNTL_H # include <fcntl.h> #endif #ifndef AT_FDCWD # define AT_FDCWD 0 #endif #ifdef ENAMETOOLONG # define is_ENAMETOOLONG(x) ((x) == ENAMETOOLONG) #else # define is_ENAMETOOLONG(x) 0 #endif /* Don't get link errors because mkdir is redefined to rpl_mkdir. */ #undef mkdir #ifndef S_IRWXU # define S_IRWXU 0700 #endif /* The length of this name must be 8. */ #define DIR_NAME "confdir3" #define DIR_NAME_LEN 8 #define DIR_NAME_SIZE (DIR_NAME_LEN + 1) /* The length of "../". */ #define DOTDOTSLASH_LEN 3 /* Leftover bytes in the buffer, to work around library or OS bugs. */ #define BUF_SLOP 20 int main (void) { #ifndef PATH_MAX /* The Hurd doesn't define this, so getcwd can't exhibit the bug -- at least not on a local file system. And if we were to start worrying about remote file systems, we'd have to enable the wrapper function all of the time, just to be safe. That's not worth the cost. */ exit (0); #elif ((INT_MAX / (DIR_NAME_SIZE / DOTDOTSLASH_LEN + 1) \ - DIR_NAME_SIZE - BUF_SLOP) \ <= PATH_MAX) /* FIXME: Assuming there's a system for which this is true, this should be done in a compile test. */ exit (0); #else char buf[PATH_MAX * (DIR_NAME_SIZE / DOTDOTSLASH_LEN + 1) + DIR_NAME_SIZE + BUF_SLOP]; char *cwd = getcwd (buf, PATH_MAX); size_t initial_cwd_len; size_t cwd_len; int fail = 0; size_t n_chdirs = 0; if (cwd == NULL) exit (1); cwd_len = initial_cwd_len = strlen (cwd); while (1) { size_t dotdot_max = PATH_MAX * (DIR_NAME_SIZE / DOTDOTSLASH_LEN); char *c = NULL; cwd_len += DIR_NAME_SIZE; /* If mkdir or chdir fails, be pessimistic and consider that as a failure, too. */ if (mkdir (DIR_NAME, S_IRWXU) < 0 || chdir (DIR_NAME) < 0) { fail = 2; break; } if (PATH_MAX <= cwd_len && cwd_len < PATH_MAX + DIR_NAME_SIZE) { c = getcwd (buf, PATH_MAX); if (!c && errno == ENOENT) { fail = 1; break; } if (c || ! (errno == ERANGE || is_ENAMETOOLONG (errno))) { fail = 2; break; } } if (dotdot_max <= cwd_len - initial_cwd_len) { if (dotdot_max + DIR_NAME_SIZE < cwd_len - initial_cwd_len) break; c = getcwd (buf, cwd_len + 1); if (!c) { if (! (errno == ERANGE || errno == ENOENT || is_ENAMETOOLONG (errno))) { fail = 2; break; } if (AT_FDCWD || errno == ERANGE || errno == ENOENT) { fail = 1; break; } } } if (c && strlen (c) != cwd_len) { fail = 2; break; } ++n_chdirs; } /* Leaving behind such a deep directory is not polite. So clean up here, right away, even though the driving shell script would also clean up. */ { size_t i; /* Unlink first, in case the chdir failed. */ unlink (DIR_NAME); for (i = 0; i <= n_chdirs; i++) { if (chdir ("..") < 0) break; rmdir (DIR_NAME); } } exit (fail); #endif }
The only way to resolve this, is to not deny access if we cannot check paths any more (better ideas??). This is not 100% safe, but I think it should suffuse, as 99.999999% of everything except for this test will not use such a long path name. Will be fixed in next version. ----- Index: libsandbox.c =================================================================== --- libsandbox.c (revision 108) +++ libsandbox.c (working copy) @@ -106,7 +106,8 @@ * failure. */ #define canonicalize_int(path, resolved_path) \ { \ - if (0 != canonicalize(path, resolved_path)) \ + /* Do not fail if the canonicalized path is too long, bug #94630 */ \ + if (0 != canonicalize(path, resolved_path, 0)) \ return -1; \ } @@ -114,7 +115,8 @@ * failure. */ #define canonicalize_ptr(path, resolved_path) \ { \ - if (0 != canonicalize(path, resolved_path)) \ + /* Do not fail if the canonicalized path is too long, bug #94630 */ \ + if (0 != canonicalize(path, resolved_path, 0)) \ return NULL; \ } @@ -150,7 +152,7 @@ static void init_wrappers(void); static void *get_dlsym(const char *, const char *); -static int canonicalize(const char *, char *); +static int canonicalize(const char *, char *, int); static char *filter_path(const char *, int); static int check_access(sbcontext_t *, const char *, const char *, const char *); static int check_syscall(sbcontext_t *, const char *, const char *); @@ -326,7 +328,13 @@ errno = old_errno; } -static int canonicalize(const char *path, char *resolved_path) +/* Be default we will fail if the path name we try to canonicalize is too long. + * This however could cause issues with some things (bug #94630 and #21766), so + * if fail_nametoolong == 0, return a null length string and do not fail. + * FIXME: This is really not *safe* if you belong to the sandbox should avoid + * any exploits that might want to touch the fs during compile ... + */ +static int canonicalize(const char *path, char *resolved_path, int fail_nametoolong) { int old_errno = errno; char *retval; @@ -352,10 +360,26 @@ * to the current working directory if it was not * an absolute path */ - if (errno == ENAMETOOLONG) - return -1; + + if (ENAMETOOLONG == errno) { + if (0 == fail_nametoolong) { + errno = 0; + *resolved_path = '\0'; + return 0; + } else { + return -1; + } + } - egetcwd(resolved_path, SB_PATH_MAX - 2); + if (NULL == egetcwd(resolved_path, SB_PATH_MAX - 2)) { + if (ENAMETOOLONG == errno && 0 == fail_nametoolong) { + errno = 0; + *resolved_path = '\0'; + return 0; + } else { + return -1; + } + } strcat(resolved_path, "/"); strncat(resolved_path, path, SB_PATH_MAX - 1); @@ -396,7 +420,7 @@ return NULL; if (0 == follow_link) { - if (-1 == canonicalize(path, filtered_path)) + if (-1 == canonicalize(path, filtered_path, 1)) goto error; } else { /* Basically we get the realpath which should resolve symlinks, @@ -414,7 +438,7 @@ * parent directory */ if (NULL == realpath(dname, filtered_path)) { /* Fall back to canonicalize */ - if (-1 == canonicalize(path, filtered_path)) { + if (-1 == canonicalize(path, filtered_path, 1)) { free(tmp_str1); goto error; } @@ -1435,10 +1459,15 @@ char *write = getenv("SANDBOX_WRITE"); char *predict = getenv("SANDBOX_PREDICT"); - if (!strlen(file)) { + if (NULL == file || 0 == strlen(file)) { /* The file/directory does not exist */ +#if 0 errno = ENOENT; return 0; +#else + /* XXX: We do not care - let the function handle it */ + return 1; +#endif } if(sb_init == 0) {