[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Re: Please accept unzoo into sarge



> --- Ursprüngliche Nachricht ---
> Von: Joey Hess <joeyh@debian.org>
> An: Thomas Schoepf <schoepf-debian@gmx.de>
> Kopie: debian-release@lists.debian.org, 306164@bugs.debian.org
> Betreff: Re: Please accept unzoo into sarge
> Datum: Tue, 17 May 2005 11:37:09 -0400

> Thomas Schoepf wrote:
> +            char patu [sizeof(Entry.diru) + sizeof(Entry.namu) + 1];
> +            strcpy( patu, Entry.diru );
> +            if ( strlen(patu) && patu[strlen(patu)-1] != '/') strcat(
> patu, "/" );
> +            strcat( patu, (Entry.lnamu ? Entry.namu : Entry.nams) );
> 
> This has an off-by-one error; I don't know if it's exploitable or even a
> problem, but if Entry.diru does not end with a trailing slash,the "+ 1"
> does not add enough space for both the trailing slash and terminating
> NULL.

Ah, I missed that, thanks.
Both Entry.diru and Entry.namu are fixed-size arrays. So in case they are
both completely filled, patu would have been to small.

> Also, if Entry.nams can be longer than Entry.namu, then the last line will
> overflow the buffer.

It can't be longer. The definition is:

 char                nams [14];
 char                namu [256];



> +                q = patu;
> +
> +                while ( !strncmp(q, "/../", 4) ) {
> +                    q += 3;
> +                }
> 
> This seems to be buggy. The code is supposed to be finding the first
> occurance of "/../" in patu, to remove it. But follow through the while
> loop with a patu of "foo/bar/../baz" and see what happens to q:

The 4 lines of code skip over "/../" substrings at the beginning of patu,
e.g. if q = "/../../../dir/file" then q will end up pointing to "dir/file".

The removal of "/../" substrings in the middle of patu is done by the next
while-loop.

> +                /* patu contains the sanitized 'universal' path, i.e.    
>  */
> +                /* separated by '/' characters, including the file name. 
>  */
> +
> +                char *f = strrchr( patu, '/' );
> +                *f++ = '\0';
> +                /* Now, patu points to the directory part, f to the file 
>  */
> +
> +                strcpy( Entry.diru, patu );
> +                strcpy( (Entry.lnamu ? Entry.namu : Entry.nams), f );
> 
> I haven't bothered to find a way to exploit it, but this strikes
> me as extremely dangerous. patu has been subject to various tranformations

I added some checks to the latest patch (attached) where I split patu into
the buffers for the directory part and the filename part again. After that
point the buffers are all big enough to hold the strings.

Thomas

-- 
Weitersagen: GMX DSL-Flatrates mit Tempo-Garantie!
Ab 4,99 Euro/Monat: http://www.gmx.net/de/go/dsl
--- unzoo.c.orig	2005-05-17 19:47:18.566367000 +0200
+++ unzoo.c	2005-05-17 19:45:18.266655640 +0200
@@ -2479,7 +2479,7 @@
 
 /****************************************************************************
 **
-*F  ExtrArch(<bim>,<out>,<ovr>,<pre>,<arc>,<filec>,<files>) . extract members
+*F  ExtrArch(<bim>,<out>,<ovr>,<pre>,<frc>,<arc>,<filec>,<files>) . extract members
 **
 **  'ExtrArch' extracts the members  of the archive with  the name <arc> that
 **  match one  of the file name  patterns '<files>[0] .. <files>[<filec>-1]'.
@@ -2491,12 +2491,15 @@
 **  stdout, i.e., to the screen.  and if it  is 2, the members are extracted.
 **  If <ovr> is 0, members will not overwrite  existing files; otherwise they
 **  will.  <pre> is a prefix that is prepended to all path names.
+**  <frc> is 1 if the user requested extraction of members even if this would
+** result in a directory traversal.
 */
-int             ExtrArch ( bim, out, ovr, pre, arc, filec, files )
+int             ExtrArch ( bim, out, ovr, pre, frc, arc, filec, files )
     unsigned long       bim;
     unsigned long       out;
     unsigned long       ovr;
     char *              pre;
+    unsigned long       frc;
     char *              arc;
     unsigned long       filec;
     char *              files [];
@@ -2582,6 +2585,102 @@
             continue;
         }
 
+        /* check the path for directory traversal                          */
+        if (frc != 1) {
+            /* but only if the user did not request otherwise              */
+
+            /* building the universal path of this member                  */
+            char patu [sizeof(Entry.diru) + sizeof(Entry.namu) + 2];
+            strcpy( patu, Entry.diru );
+            if ( strlen(patu) && patu[strlen(patu)-1] != '/') strcat( patu, "/" );
+            strcat( patu, (Entry.lnamu ? Entry.namu : Entry.nams) );
+
+            int found_trav = 0;
+
+            if ( strstr( patu, "/../" )) {
+                found_trav = 1;
+
+                /* remove "/../" from the path                             */
+                char tmp [sizeof(patu)];
+                char *p;
+                char *q;
+                memset(tmp, 0, sizeof(tmp));
+                q = patu;
+
+                while ( !strncmp(q, "/../", 4) ) {
+                    q += 3;
+                }
+                if (q[0] == '/') q++;
+
+                while ((p = strstr( q, "/../" )) != NULL) {
+                    if (q[0] == '/') q++;
+                    if (p > q) strncat(tmp, q, p-q);
+                    if (tmp[strlen(tmp)-1] != '/') strcat(tmp, "/");
+                    p += 3;
+                    q = p;
+                }
+                strncat(tmp, q+1, patu + strlen(patu) - q);
+                strcpy(patu, tmp);
+
+                printf("unzoo: skipped \"/../\" path component(s) in '%s'\n", Entry.patl);
+            }
+            if ( *patu == '/' && !strlen( pre ) ) {
+                found_trav = 1;
+
+                char *p = malloc(sizeof(patu));
+                char *q = p;
+                memset(p, 0, sizeof(patu));
+                strcpy(p, patu);
+                while ( q[0] == '/' ) q++;
+                strcpy(patu, q);
+                free(p);
+
+                printf("unzoo: skipped root directory path component in '%s'\n", patl);
+            }
+            if ( !strncmp( patu, "../", 3 )) {
+                found_trav = 1;
+
+                char tmp [sizeof(patu)];
+                memset(tmp, 0, sizeof(tmp));
+                strcpy(tmp, patu + 3);
+                strcpy(patu, tmp);
+
+                printf("unzoo: skipped \"../\" path component in '%s'\n", patl);
+            }
+
+            if (found_trav) {
+                /* patu contains the sanitized 'universal' path, i.e.      */
+                /* separated by '/' characters, including the file name.   */
+
+                char *f = strrchr( patu, '/' );
+                *f++ = '\0';
+                /* Now, patu points to the directory part, f to the file   */
+
+                memset( Entry.diru, 0, sizeof(Entry.diru) );
+                strncpy( Entry.diru, patu, sizeof(Entry.diru)-1 );
+                if ( Entry.lnamu > 0 ) {
+                    memset( Entry.namu, 0, sizeof(Entry.namu) );
+                    strncpy( Entry.namu, f, sizeof(Entry.namu)-1 );
+                } else {
+                    memset( Entry.nams, 0, sizeof(Entry.nams) );
+                    strncpy( Entry.nams, f, sizeof(Entry.nams)-1 );
+                }
+
+                /* convert the names to local format                       */
+                if ( Entry.system == 0 || Entry.system == 2 ) {
+                    CONV_DIRE( Entry.dirl, Entry.diru );
+                    CONV_NAME( Entry.naml, (Entry.lnamu ? Entry.namu : Entry.nams) );
+                }
+                else {
+                    strcpy( Entry.dirl, Entry.diru );
+                    strcpy( Entry.naml, (Entry.lnamu ? Entry.namu : Entry.nams) );
+                }
+                /* sizeof(patl)=512, sizeof({dirl|naml}=256} */
+                strcpy( Entry.patl, Entry.dirl );
+                strcat( Entry.patl, Entry.naml );
+            }
+        }
+
         /* check that such a file does not already exist                   */
         strcpy( patl, pre );  strcat( patl, Entry.patl );
         if ( out == 2 && ovr == 0 && OpenReadFile(patl,0L) ) {
@@ -2740,7 +2839,7 @@
     printf("  <file>: list only files matching at least one pattern,\n");
     printf("          '?' matches any char, '*' matches any string.\n");
     printf("\n");
-    printf("unzoo -x [-abnpo] [-j <prefix>] <archive>[.zoo] [<file>..]\n");
+    printf("unzoo -x [-abnpo] [-t] [-j <prefix>] <archive>[.zoo] [<file>..]\n");
     printf("  extract the members of the archive\n");
     printf("  -a:  extract all members as text files ");
     printf("(not only those with !TEXT! comments)\n");
@@ -2750,6 +2849,10 @@
     printf("  -p:  extract to stdout\n");
     printf("  -o:  extract over existing files\n");
     printf("  -j:  extract to '<prefix><membername>'\n");
+    printf("  -f:  force extraction of members to their original locations\n");
+    printf("       even if this results in files extracted outside the\n");
+    printf("       working directory. THIS COULD POTENTIALLY OVERWRITE\n");
+    printf("       IMPORTANT FILES, SO USE WITH CARE!\n");
     printf("  <file>: extract only files matching at least one pattern,\n");
     printf("          '?' matches any char, '*' matches any string.\n");
     return 1;
@@ -2773,6 +2876,7 @@
     unsigned long       bim;            /* extraction mode option          */
     unsigned long       out;            /* output destination option       */
     unsigned long       ovr;            /* overwrite file option           */
+    unsigned long       frc;            /* force extraction option         */
     char *              pre;            /* prefix to prepend to path names */
     char                argl [256];     /* interactive command line        */
     int                 argd;           /* interactive command count       */
@@ -2792,7 +2896,7 @@
     do {
 
         /* scan the command line arguments                                 */
-        cmd = 1;  ver = 0;  bim = 0;  out = 2;  ovr = 0;
+        cmd = 1;  ver = 0;  bim = 0;  out = 2;  ovr = 0; frc = 0;
         pre = "";
         while ( 1 < argc && argv[1][0] == '-' ) {
             if ( argv[1][2] != '\0' )  cmd = 0;
@@ -2802,6 +2906,7 @@
             case 'x': case 'X': if ( cmd != 0 )  cmd = 2;            break;
             case 'a': case 'A': if ( cmd != 2 )  cmd = 0;  bim = 1;  break;
             case 'b': case 'B': if ( cmd != 2 )  cmd = 0;  bim = 2;  break;
+            case 'f': case 'F': if ( cmd != 2 )  cmd = 0;  frc = 1; break;
             case 'n': case 'N': if ( cmd != 2 )  cmd = 0;  out = 0;  break;
             case 'p': case 'P': if ( cmd != 2 )  cmd = 0;  out = 1;  break;
             case 'o': case 'O': if ( cmd != 2 )  cmd = 0;  ovr = 1;  break;
@@ -2818,7 +2923,7 @@
             res = ListArch( ver, argv[1],
                             (unsigned long)argc-2, argv+2 );
         else if ( cmd == 2 && 1 < argc )
-            res = ExtrArch( bim, out, ovr, pre, argv[1],
+            res = ExtrArch( bim, out, ovr, pre, frc, argv[1],
                             (unsigned long)argc-2, argv+2 );
         else
             res = HelpArch();

Reply to: