Subject: unace: multiple buffer overflows and directory traversal bugs Package: unace Version: 1.2b-2 Severity: grave Justification: user security hole Tags: security patch
I have found multiple security vulnerabilities in unace. There are two buffer overflows when extracting, testing or listing specially prepared ACE archives. They are caused by wrong usage of strncpy() with the third parameter coming from the archive. In both cases, the attacker controls the EIP register. There are also two buffer overflows when (a) dealing with long (>15600 characters) command line arguments for archive names, and (b) when preparing a string for printing Ready for next volume messages. Furthermore, there are directory traversal bugs when extracting ACE archives. They are both of the absolute ("/etc/nologin") and the relative ("../../../../../../../etc/nologin") type. All buffer overflows have the identifier CAN-2005-0160, and the directory traversal bugs have the identifier CAN-2005-0161. I have attached some test archives and a patch. // Ulf H�rnhammar for the Debian Security Audit Project http://www.debian.org/security/audit/ -- System Information: Debian Release: 3.1 APT prefers testing APT policy: (500, 'testing') Architecture: i386 (i686) Kernel: Linux 2.6.8-2-686 Locale: LANG=en_US, LC_CTYPE=en_US (charmap=ISO-8859-1) Versions of packages unace depends on: ii libc6 2.3.2.ds1-20 GNU C Library: Shared libraries an -- no debconf information
--- uac_crt.h.old 1998-07-01 10:29:00.000000000 +0200 +++ uac_crt.h 2005-02-14 00:48:35.000000000 +0100 @@ -4,7 +4,7 @@ #include "acestruc.h" -CHAR *ace_fname(CHAR * s, thead * head, INT nopath); +CHAR *ace_fname(CHAR * s, thead * head, INT nopath, unsigned int size); INT create_dest_file(CHAR * file, INT a); #ifdef UNIX --- uac_crt.c.old 1998-07-01 10:29:00.000000000 +0200 +++ uac_crt.c 2005-02-14 02:46:02.000000000 +0100 @@ -33,12 +33,15 @@ /* gets file name from header */ -CHAR *ace_fname(CHAR * s, thead * head, INT nopath) +CHAR *ace_fname(CHAR * s, thead * head, INT nopath, unsigned int size) { - INT i; + unsigned int i; char *cp; - strncpy(s, (*(tfhead *) head).FNAME, i = (*(tfhead *) head).FNAME_SIZE); + i = (*(tfhead *) head).FNAME_SIZE; + if (i > (size - 1)) + i = size - 1; + strncpy(s, (*(tfhead *) head).FNAME, i); s[i] = 0; if (nopath) @@ -56,22 +59,72 @@ CHAR *ace_fname(CHAR * s, thead * head, } #endif + cp = s; + while (*cp == '/') cp++; + if (cp != s) + memmove(s, cp, strlen(cp) + 1); + return s; } +int is_directory_traversal(char *str) +{ + unsigned int mode, countdots; + /* mode 0 = fresh, 1 = just dots, 2 = not just dots */ + char ch; + + mode = countdots = 0; + + while (ch = *str++) + { + if ((ch == '/') && (mode == 1) && (countdots > 1)) + return 1; + + if (ch == '/') + { + mode = countdots = 0; + continue; + } + + if (ch == '.') + { + if (mode == 0) + mode = 1; + + countdots++; + } + else + mode = 2; + } + + if ((mode == 1) && (countdots > 1)) + return 1; + + return 0; +} + void check_ext_dir(CHAR * f) // checks/creates path of file { CHAR *cp, d[PATH_MAX]; - INT i; + unsigned int i; d[0] = 0; + if (is_directory_traversal(f)) + { + f_err = ERR_WRITE; + printf("\n Directory traversal attempt: %s\n", f); + return; + } + for (;;) { if ((cp = (CHAR *) strchr(&f[strlen(d) + 1], DIRSEP))!=NULL) { i = cp - f; + if (i > (PATH_MAX - 1)) + i = PATH_MAX - 1; strncpy(d, f, i); d[i] = 0; } --- unace.c.old 1998-07-01 10:29:00.000000000 +0200 +++ unace.c 2005-02-14 01:43:28.000000000 +0100 @@ -240,6 +240,7 @@ INT read_arc_head(void) // searc INT open_archive(INT print_err) // opens archive (or volume) { CHAR av_str[80]; + unsigned int copylen; archan = open(aname, O_RDONLY | O_BINARY); // open file @@ -263,8 +264,11 @@ INT open_archive(INT print_err) sprintf(av_str, "\ncreated on %d.%d.%d by ", ts_day(adat.time_cr), ts_month(adat.time_cr), ts_year(adat.time_cr)); printf(av_str); - strncpy(av_str, mhead.AV, mhead.AV_SIZE); - av_str[mhead.AV_SIZE] = 0; + copylen = mhead.AV_SIZE; + if (copylen > 79) + copylen = 79; + strncpy(av_str, mhead.AV, copylen); + av_str[copylen] = 0; printf("%s\n\n", av_str); } comment_out("Main comment:"); // print main comment @@ -300,7 +304,7 @@ void get_next_volname(void) INT proc_vol(void) // opens volume { INT i; - CHAR s[80]; + CHAR s[PATH_MAX + 80]; // if f_allvol_pr is 2 we have -y and should never ask if ((!fileexists_insense(aname) && f_allvol_pr != 2) || !f_allvol_pr) @@ -428,7 +432,7 @@ void extract_files(int nopath, int test) if (head.HEAD_TYPE == FILE_BLK) { comment_out("File comment:"); // show file comment - ace_fname(file, &head, nopath); // get file name + ace_fname(file, &head, nopath, sizeof(file)); // get file name printf("\n%s", file); flush; dcpr_init_file(); // initialize decompression of file @@ -496,7 +500,7 @@ void list_files(int verbose) if (head.HEAD_TYPE == FILE_BLK) { ULONG ti=fhead.FTIME; - ace_fname(file, &head, verbose ? 0 : 1); // get file name + ace_fname(file, &head, verbose ? 0 : 1, sizeof(file)); // get file name size += fhead.SIZE; psize += @@ -588,7 +592,8 @@ int main(INT argc, CHAR * argv[]) init_unace(); // initialize unace - strcpy(aname, argv[arg_cnt]); // get archive name + strncpy(aname, argv[arg_cnt], sizeof(aname) - 4); // get archive name + aname[sizeof(aname) - 5] = '\0'; if (!(s = (CHAR *) strrchr(aname, DIRSEP))) s = aname; if (!strrchr(s, '.'))
bufoflow1.ace
Description: Binary data
bufoflow2.ace
Description: Binary data
dirtraversal1.ace
Description: Binary data
dirtraversal2.ace
Description: Binary data