Edit

kc3-lang/zlib/contrib/minizip/miniunz.c

Branch :

  • Show log

    Commit

  • Author : Matt Wilson
    Date : 2024-01-17 14:46:18
    Hash : 14a5f8f2
    Message : Neutralize zip file traversal attacks in miniunz. Archive formats such as .zip files are generally susceptible to so-called "traversal attacks". This allows an attacker to craft an archive that writes to unexpected locations of the file system (e.g., /etc/shadow) if an unspecting root user were to unpack a malicious archive. This patch neutralizes absolute paths such as /tmp/moo and deeply relative paths such as dummy/../../../../../../../../../../tmp/moo The Debian project requested CVE-2014-9485 be allocated for the first identified weakness. The fix was incomplete, resulting in a revised patch applied here. Since there wasn't an updated version released by Debian with the incomplete fix, I suggest we use this CVE to identify both issues. Link: https://security.snyk.io/research/zip-slip-vulnerability Link: https://bugs.debian.org/774321 Link: https://bugs.debian.org/776831 Link: https://nvd.nist.gov/vuln/detail/CVE-2014-9485 Reported-by: Jakub Wilk <jwilk@debian.org> Fixed-by: Michael Gilbert <mgilbert@debian.org>

  • contrib/minizip/miniunz.c
  • /*
       miniunz.c
       Version 1.1, February 14h, 2010
       sample part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html )
    
             Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html )
    
             Modifications of Unzip for Zip64
             Copyright (C) 2007-2008 Even Rouault
    
             Modifications for Zip64 support on both zip and unzip
             Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com )
    */
    
    #if (!defined(_WIN32)) && (!defined(WIN32)) && (!defined(__APPLE__))
            #ifndef __USE_FILE_OFFSET64
                    #define __USE_FILE_OFFSET64
            #endif
            #ifndef __USE_LARGEFILE64
                    #define __USE_LARGEFILE64
            #endif
            #ifndef _LARGEFILE64_SOURCE
                    #define _LARGEFILE64_SOURCE
            #endif
            #ifndef _FILE_OFFSET_BIT
                    #define _FILE_OFFSET_BIT 64
            #endif
    #endif
    
    #if defined(__APPLE__) || defined(__HAIKU__) || defined(MINIZIP_FOPEN_NO_64)
    // In darwin and perhaps other BSD variants off_t is a 64 bit value, hence no need for specific 64 bit functions
    #define FOPEN_FUNC(filename, mode) fopen(filename, mode)
    #define FTELLO_FUNC(stream) ftello(stream)
    #define FSEEKO_FUNC(stream, offset, origin) fseeko(stream, offset, origin)
    #else
    #define FOPEN_FUNC(filename, mode) fopen64(filename, mode)
    #define FTELLO_FUNC(stream) ftello64(stream)
    #define FSEEKO_FUNC(stream, offset, origin) fseeko64(stream, offset, origin)
    #endif
    
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <time.h>
    #include <errno.h>
    #include <fcntl.h>
    #include <sys/stat.h>
    
    #ifdef _WIN32
    # include <direct.h>
    # include <io.h>
    #else
    # include <unistd.h>
    # include <utime.h>
    #endif
    
    
    #include "unzip.h"
    
    #define CASESENSITIVITY (0)
    #define WRITEBUFFERSIZE (8192)
    #define MAXFILENAME (256)
    
    #ifdef _WIN32
    #define USEWIN32IOAPI
    #include "iowin32.h"
    #endif
    /*
      mini unzip, demo of unzip package
    
      usage :
      Usage : miniunz [-exvlo] file.zip [file_to_extract] [-d extractdir]
    
      list the file in the zipfile, and print the content of FILE_ID.ZIP or README.TXT
        if it exists
    */
    
    
    /* change_file_date : change the date/time of a file
        filename : the filename of the file where date/time must be modified
        dosdate : the new date at the MSDOS format (4 bytes)
        tmu_date : the SAME new date at the tm_unz format */
    static void change_file_date(const char *filename, uLong dosdate, tm_unz tmu_date) {
    #ifdef _WIN32
      HANDLE hFile;
      FILETIME ftm,ftLocal,ftCreate,ftLastAcc,ftLastWrite;
    
      hFile = CreateFileA(filename,GENERIC_READ | GENERIC_WRITE,
                          0,NULL,OPEN_EXISTING,0,NULL);
      GetFileTime(hFile,&ftCreate,&ftLastAcc,&ftLastWrite);
      DosDateTimeToFileTime((WORD)(dosdate>>16),(WORD)dosdate,&ftLocal);
      LocalFileTimeToFileTime(&ftLocal,&ftm);
      SetFileTime(hFile,&ftm,&ftLastAcc,&ftm);
      CloseHandle(hFile);
    #else
    #if defined(unix) || defined(__APPLE__)
      (void)dosdate;
      struct utimbuf ut;
      struct tm newdate;
      newdate.tm_sec = tmu_date.tm_sec;
      newdate.tm_min=tmu_date.tm_min;
      newdate.tm_hour=tmu_date.tm_hour;
      newdate.tm_mday=tmu_date.tm_mday;
      newdate.tm_mon=tmu_date.tm_mon;
      if (tmu_date.tm_year > 1900)
          newdate.tm_year=tmu_date.tm_year - 1900;
      else
          newdate.tm_year=tmu_date.tm_year ;
      newdate.tm_isdst=-1;
    
      ut.actime=ut.modtime=mktime(&newdate);
      utime(filename,&ut);
    #else
      (void)filename;
      (void)dosdate;
      (void)tmu_date;
    #endif
    #endif
    }
    
    
    /* mymkdir and change_file_date are not 100 % portable
       As I don't know well Unix, I wait feedback for the unix portion */
    
    static int mymkdir(const char* dirname) {
        int ret=0;
    #ifdef _WIN32
        ret = _mkdir(dirname);
    #elif unix
        ret = mkdir (dirname,0775);
    #elif __APPLE__
        ret = mkdir (dirname,0775);
    #else
        (void)dirname;
    #endif
        return ret;
    }
    
    static int makedir(const char *newdir) {
      char *buffer ;
      char *p;
      size_t len = strlen(newdir);
    
      if (len == 0)
        return 0;
    
      buffer = (char*)malloc(len+1);
            if (buffer==NULL)
            {
                    printf("Error allocating memory\n");
                    return UNZ_INTERNALERROR;
            }
      strcpy(buffer,newdir);
    
      if (buffer[len-1] == '/') {
        buffer[len-1] = '\0';
      }
      if (mymkdir(buffer) == 0)
        {
          free(buffer);
          return 1;
        }
    
      p = buffer+1;
      while (1)
        {
          char hold;
    
          while(*p && *p != '\\' && *p != '/')
            p++;
          hold = *p;
          *p = 0;
          if ((mymkdir(buffer) == -1) && (errno == ENOENT))
            {
              printf("couldn't create directory %s\n",buffer);
              free(buffer);
              return 0;
            }
          if (hold == 0)
            break;
          *p++ = hold;
        }
      free(buffer);
      return 1;
    }
    
    static void do_banner(void) {
        printf("MiniUnz 1.1, demo of zLib + Unz package written by Gilles Vollant\n");
        printf("more info at http://www.winimage.com/zLibDll/unzip.html\n\n");
    }
    
    static void do_help(void) {
        printf("Usage : miniunz [-e] [-x] [-v] [-l] [-o] [-p password] file.zip [file_to_extr.] [-d extractdir]\n\n" \
               "  -e  Extract without pathname (junk paths)\n" \
               "  -x  Extract with pathname\n" \
               "  -v  list files\n" \
               "  -l  list files\n" \
               "  -d  directory to extract into\n" \
               "  -o  overwrite files without prompting\n" \
               "  -p  extract encrypted file using password\n\n");
    }
    
    static void Display64BitsSize(ZPOS64_T n, int size_char) {
      /* to avoid compatibility problem , we do here the conversion */
      char number[21];
      int offset=19;
      int pos_string = 19;
      number[20]=0;
      for (;;) {
          number[offset]=(char)((n%10)+'0');
          if (number[offset] != '0')
              pos_string=offset;
          n/=10;
          if (offset==0)
              break;
          offset--;
      }
      {
          int size_display_string = 19-pos_string;
          while (size_char > size_display_string)
          {
              size_char--;
              printf(" ");
          }
      }
    
      printf("%s",&number[pos_string]);
    }
    
    static int do_list(unzFile uf) {
        uLong i;
        unz_global_info64 gi;
        int err;
    
        err = unzGetGlobalInfo64(uf,&gi);
        if (err!=UNZ_OK)
            printf("error %d with zipfile in unzGetGlobalInfo \n",err);
        printf("  Length  Method     Size Ratio   Date    Time   CRC-32     Name\n");
        printf("  ------  ------     ---- -----   ----    ----   ------     ----\n");
        for (i=0;i<gi.number_entry;i++)
        {
            char filename_inzip[256];
            unz_file_info64 file_info;
            uLong ratio=0;
            const char *string_method = "";
            char charCrypt=' ';
            err = unzGetCurrentFileInfo64(uf,&file_info,filename_inzip,sizeof(filename_inzip),NULL,0,NULL,0);
            if (err!=UNZ_OK)
            {
                printf("error %d with zipfile in unzGetCurrentFileInfo\n",err);
                break;
            }
            if (file_info.uncompressed_size>0)
                ratio = (uLong)((file_info.compressed_size*100)/file_info.uncompressed_size);
    
            /* display a '*' if the file is encrypted */
            if ((file_info.flag & 1) != 0)
                charCrypt='*';
    
            if (file_info.compression_method==0)
                string_method="Stored";
            else
            if (file_info.compression_method==Z_DEFLATED)
            {
                uInt iLevel=(uInt)((file_info.flag & 0x6)/2);
                if (iLevel==0)
                  string_method="Defl:N";
                else if (iLevel==1)
                  string_method="Defl:X";
                else if ((iLevel==2) || (iLevel==3))
                  string_method="Defl:F"; /* 2:fast , 3 : extra fast*/
            }
            else
            if (file_info.compression_method==Z_BZIP2ED)
            {
                  string_method="BZip2 ";
            }
            else
                string_method="Unkn. ";
    
            Display64BitsSize(file_info.uncompressed_size,7);
            printf("  %6s%c",string_method,charCrypt);
            Display64BitsSize(file_info.compressed_size,7);
            printf(" %3lu%%  %2.2lu-%2.2lu-%2.2lu  %2.2lu:%2.2lu  %8.8lx   %s\n",
                    ratio,
                    (uLong)file_info.tmu_date.tm_mon + 1,
                    (uLong)file_info.tmu_date.tm_mday,
                    (uLong)file_info.tmu_date.tm_year % 100,
                    (uLong)file_info.tmu_date.tm_hour,(uLong)file_info.tmu_date.tm_min,
                    (uLong)file_info.crc,filename_inzip);
            if ((i+1)<gi.number_entry)
            {
                err = unzGoToNextFile(uf);
                if (err!=UNZ_OK)
                {
                    printf("error %d with zipfile in unzGoToNextFile\n",err);
                    break;
                }
            }
        }
    
        return 0;
    }
    
    
    static int do_extract_currentfile(unzFile uf, const int* popt_extract_without_path, int* popt_overwrite, const char* password) {
        char filename_inzip[256];
        char* filename_withoutpath;
        char* p;
        int err=UNZ_OK;
        FILE *fout=NULL;
        void* buf;
        uInt size_buf;
    
        unz_file_info64 file_info;
        err = unzGetCurrentFileInfo64(uf,&file_info,filename_inzip,sizeof(filename_inzip),NULL,0,NULL,0);
    
        if (err!=UNZ_OK)
        {
            printf("error %d with zipfile in unzGetCurrentFileInfo\n",err);
            return err;
        }
    
        size_buf = WRITEBUFFERSIZE;
        buf = (void*)malloc(size_buf);
        if (buf==NULL)
        {
            printf("Error allocating memory\n");
            return UNZ_INTERNALERROR;
        }
    
        p = filename_withoutpath = filename_inzip;
        while ((*p) != '\0')
        {
            if (((*p)=='/') || ((*p)=='\\'))
                filename_withoutpath = p+1;
            p++;
        }
    
        if ((*filename_withoutpath)=='\0')
        {
            if ((*popt_extract_without_path)==0)
            {
                printf("creating directory: %s\n",filename_inzip);
                mymkdir(filename_inzip);
            }
        }
        else
        {
            const char* write_filename;
            int skip=0;
    
            if ((*popt_extract_without_path)==0)
                write_filename = filename_inzip;
            else
                write_filename = filename_withoutpath;
    
            if (write_filename[0]!='\0')
            {
                const char* relative_check = write_filename;
                while (relative_check[1]!='\0')
                {
                    if (relative_check[0]=='.' && relative_check[1]=='.')
                        write_filename = relative_check;
                    relative_check++;
                }
            }
    
            while (write_filename[0]=='/' || write_filename[0]=='.')
                write_filename++;
    
            err = unzOpenCurrentFilePassword(uf,password);
            if (err!=UNZ_OK)
            {
                printf("error %d with zipfile in unzOpenCurrentFilePassword\n",err);
            }
    
            if (((*popt_overwrite)==0) && (err==UNZ_OK))
            {
                char rep=0;
                FILE* ftestexist;
                ftestexist = FOPEN_FUNC(write_filename,"rb");
                if (ftestexist!=NULL)
                {
                    fclose(ftestexist);
                    do
                    {
                        char answer[128];
                        int ret;
    
                        printf("The file %s exists. Overwrite ? [y]es, [n]o, [A]ll: ",write_filename);
                        ret = scanf("%1s",answer);
                        if (ret != 1)
                        {
                           exit(EXIT_FAILURE);
                        }
                        rep = answer[0] ;
                        if ((rep>='a') && (rep<='z'))
                            rep -= 0x20;
                    }
                    while ((rep!='Y') && (rep!='N') && (rep!='A'));
                }
    
                if (rep == 'N')
                    skip = 1;
    
                if (rep == 'A')
                    *popt_overwrite=1;
            }
    
            if ((skip==0) && (err==UNZ_OK))
            {
                fout=FOPEN_FUNC(write_filename,"wb");
                /* some zipfile don't contain directory alone before file */
                if ((fout==NULL) && ((*popt_extract_without_path)==0) &&
                                    (filename_withoutpath!=(char*)filename_inzip))
                {
                    char c=*(filename_withoutpath-1);
                    *(filename_withoutpath-1)='\0';
                    makedir(write_filename);
                    *(filename_withoutpath-1)=c;
                    fout=FOPEN_FUNC(write_filename,"wb");
                }
    
                if (fout==NULL)
                {
                    printf("error opening %s\n",write_filename);
                }
            }
    
            if (fout!=NULL)
            {
                printf(" extracting: %s\n",write_filename);
    
                do
                {
                    err = unzReadCurrentFile(uf,buf,size_buf);
                    if (err<0)
                    {
                        printf("error %d with zipfile in unzReadCurrentFile\n",err);
                        break;
                    }
                    if (err>0)
                        if (fwrite(buf,(unsigned)err,1,fout)!=1)
                        {
                            printf("error in writing extracted file\n");
                            err=UNZ_ERRNO;
                            break;
                        }
                }
                while (err>0);
                if (fout)
                        fclose(fout);
    
                if (err==0)
                    change_file_date(write_filename,file_info.dosDate,
                                     file_info.tmu_date);
            }
    
            if (err==UNZ_OK)
            {
                err = unzCloseCurrentFile (uf);
                if (err!=UNZ_OK)
                {
                    printf("error %d with zipfile in unzCloseCurrentFile\n",err);
                }
            }
            else
                unzCloseCurrentFile(uf); /* don't lose the error */
        }
    
        free(buf);
        return err;
    }
    
    
    static int do_extract(unzFile uf, int opt_extract_without_path, int opt_overwrite, const char* password) {
        uLong i;
        unz_global_info64 gi;
        int err;
    
        err = unzGetGlobalInfo64(uf,&gi);
        if (err!=UNZ_OK)
            printf("error %d with zipfile in unzGetGlobalInfo \n",err);
    
        for (i=0;i<gi.number_entry;i++)
        {
            if (do_extract_currentfile(uf,&opt_extract_without_path,
                                          &opt_overwrite,
                                          password) != UNZ_OK)
                break;
    
            if ((i+1)<gi.number_entry)
            {
                err = unzGoToNextFile(uf);
                if (err!=UNZ_OK)
                {
                    printf("error %d with zipfile in unzGoToNextFile\n",err);
                    break;
                }
            }
        }
    
        return 0;
    }
    
    static int do_extract_onefile(unzFile uf, const char* filename, int opt_extract_without_path, int opt_overwrite, const char* password) {
        if (unzLocateFile(uf,filename,CASESENSITIVITY)!=UNZ_OK)
        {
            printf("file %s not found in the zipfile\n",filename);
            return 2;
        }
    
        if (do_extract_currentfile(uf,&opt_extract_without_path,
                                          &opt_overwrite,
                                          password) == UNZ_OK)
            return 0;
        else
            return 1;
    }
    
    
    int main(int argc, char *argv[]) {
        const char *zipfilename=NULL;
        const char *filename_to_extract=NULL;
        const char *password=NULL;
        char filename_try[MAXFILENAME+16] = "";
        int i;
        int ret_value=0;
        int opt_do_list=0;
        int opt_do_extract=1;
        int opt_do_extract_withoutpath=0;
        int opt_overwrite=0;
        int opt_extractdir=0;
        const char *dirname=NULL;
        unzFile uf=NULL;
    
        do_banner();
        if (argc==1)
        {
            do_help();
            return 0;
        }
        else
        {
            for (i=1;i<argc;i++)
            {
                if ((*argv[i])=='-')
                {
                    const char *p=argv[i]+1;
    
                    while ((*p)!='\0')
                    {
                        char c=*(p++);
                        if ((c=='l') || (c=='L'))
                            opt_do_list = 1;
                        if ((c=='v') || (c=='V'))
                            opt_do_list = 1;
                        if ((c=='x') || (c=='X'))
                            opt_do_extract = 1;
                        if ((c=='e') || (c=='E'))
                            opt_do_extract = opt_do_extract_withoutpath = 1;
                        if ((c=='o') || (c=='O'))
                            opt_overwrite=1;
                        if ((c=='d') || (c=='D'))
                        {
                            opt_extractdir=1;
                            dirname=argv[i+1];
                        }
    
                        if (((c=='p') || (c=='P')) && (i+1<argc))
                        {
                            password=argv[i+1];
                            i++;
                        }
                    }
                }
                else
                {
                    if (zipfilename == NULL)
                        zipfilename = argv[i];
                    else if ((filename_to_extract==NULL) && (!opt_extractdir))
                            filename_to_extract = argv[i] ;
                }
            }
        }
    
        if (zipfilename!=NULL)
        {
    
    #        ifdef USEWIN32IOAPI
            zlib_filefunc64_def ffunc;
    #        endif
    
            strncpy(filename_try, zipfilename,MAXFILENAME-1);
            /* strncpy doesn't append the trailing NULL, of the string is too long. */
            filename_try[ MAXFILENAME ] = '\0';
    
    #        ifdef USEWIN32IOAPI
            fill_win32_filefunc64A(&ffunc);
            uf = unzOpen2_64(zipfilename,&ffunc);
    #        else
            uf = unzOpen64(zipfilename);
    #        endif
            if (uf==NULL)
            {
                strcat(filename_try,".zip");
    #            ifdef USEWIN32IOAPI
                uf = unzOpen2_64(filename_try,&ffunc);
    #            else
                uf = unzOpen64(filename_try);
    #            endif
            }
        }
    
        if (uf==NULL)
        {
            printf("Cannot open %s or %s.zip\n",zipfilename,zipfilename);
            return 1;
        }
        printf("%s opened\n",filename_try);
    
        if (opt_do_list==1)
            ret_value = do_list(uf);
        else if (opt_do_extract==1)
        {
    #ifdef _WIN32
            if (opt_extractdir && _chdir(dirname))
    #else
            if (opt_extractdir && chdir(dirname))
    #endif
            {
              printf("Error changing into %s, aborting\n", dirname);
              exit(-1);
            }
    
            if (filename_to_extract == NULL)
                ret_value = do_extract(uf, opt_do_extract_withoutpath, opt_overwrite, password);
            else
                ret_value = do_extract_onefile(uf, filename_to_extract, opt_do_extract_withoutpath, opt_overwrite, password);
        }
    
        unzClose(uf);
    
        return ret_value;
    }