Gentoo Websites Logo
Go to: Gentoo Home Documentation Forums Lists Bugs Planet Store Wiki Get Gentoo!
Bug 94630 - strange sandbox behavior
Summary: strange sandbox behavior
Status: RESOLVED FIXED
Alias: None
Product: Gentoo Linux
Classification: Unclassified
Component: [OLD] Core system (show other bugs)
Hardware: x86 Linux
: High normal (vote)
Assignee: Sandbox Maintainers
URL: http://forums.gentoo.org/viewtopic-t-...
Whiteboard:
Keywords:
Depends on:
Blocks: 125701
  Show dependency tree
 
Reported: 2005-05-31 09:04 UTC by Michael Cramer
Modified: 2006-09-03 01:42 UTC (History)
0 users

See Also:
Package list:
Runtime testing required: ---


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Michael Cramer 2005-05-31 09:04:12 UTC
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
Comment 1 SpanKY gentoo-dev 2005-05-31 21:45:51 UTC
we cant really guess what package you're playing with if you dont tell us ...
Comment 2 Martin Schlemmer (RETIRED) gentoo-dev 2005-06-09 07:27:02 UTC
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
}
Comment 3 Martin Schlemmer (RETIRED) gentoo-dev 2005-06-09 07:29:23 UTC
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) {