From 41a81769e379e6b77eaf27c37a69e70056a4654f Mon Sep 17 00:00:00 2001 From: Josh Elsasser Date: Mon, 5 Mar 2007 00:07:48 +0000 Subject: [PATCH] Better checking of metainfo. Strip / out of filenames and path components. Safely handle . and .. in file paths. --- libtransmission/bencode.c | 22 ++++- libtransmission/bencode.h | 3 +- libtransmission/metainfo.c | 181 +++++++++++++++++++++++++------------ 3 files changed, 146 insertions(+), 60 deletions(-) diff --git a/libtransmission/bencode.c b/libtransmission/bencode.c index f3fb472ef..f7f9b0ac7 100644 --- a/libtransmission/bencode.c +++ b/libtransmission/bencode.c @@ -214,7 +214,7 @@ void tr_bencFree( benc_val_t * val ) } } -benc_val_t * tr_bencDictFind( benc_val_t * val, char * key ) +benc_val_t * tr_bencDictFind( benc_val_t * val, const char * key ) { int i; if( val->type != TYPE_DICT ) @@ -233,6 +233,26 @@ benc_val_t * tr_bencDictFind( benc_val_t * val, char * key ) return NULL; } +benc_val_t * tr_bencDictFindFirst( benc_val_t * val, ... ) +{ + const char * key; + benc_val_t * ret; + va_list ap; + + va_start( ap, val ); + while( ( key = va_arg( ap, const char * ) ) ) + { + ret = tr_bencDictFind( val, key ); + if( NULL != ret ) + { + break; + } + } + va_end( ap ); + + return ret; +} + char * tr_bencSaveMalloc( benc_val_t * val, int * len ) { char * buf = NULL; diff --git a/libtransmission/bencode.h b/libtransmission/bencode.h index a549cf029..e3786c251 100644 --- a/libtransmission/bencode.h +++ b/libtransmission/bencode.h @@ -56,7 +56,8 @@ int _tr_bencLoad( char * buf, int len, benc_val_t * val, char ** end ); void tr_bencPrint( benc_val_t * val ); void tr_bencFree( benc_val_t * val ); -benc_val_t * tr_bencDictFind( benc_val_t * val, char * key ); +benc_val_t * tr_bencDictFind( benc_val_t * val, const char * key ); +benc_val_t * tr_bencDictFindFirst( benc_val_t * val, ... ); char * tr_bencSaveMalloc( benc_val_t * val, int * len ); int tr_bencSave( benc_val_t * val, char ** buf, int * used, int * max ); diff --git a/libtransmission/metainfo.c b/libtransmission/metainfo.c index 78f0c03e6..6ac13310d 100644 --- a/libtransmission/metainfo.c +++ b/libtransmission/metainfo.c @@ -29,10 +29,11 @@ /*********************************************************************** * Local prototypes **********************************************************************/ +static int getfile( char * buf, int size, + const char * prefix, const benc_val_t * name ); static int getannounce( tr_info_t * inf, benc_val_t * meta ); static char * announceToScrape( const char * announce ); -#define strcatUTF8( dst, src) _strcatUTF8( (dst), sizeof( dst ) - 1, (src) ) -static void _strcatUTF8( char *, int, char * ); +static void strcatUTF8( char *, int, const char *, int ); /*********************************************************************** * tr_metainfoParse @@ -102,9 +103,10 @@ int tr_metainfoParse( tr_info_t * inf, const char * path, } /* Get info hash */ - if( !( beInfo = tr_bencDictFind( &meta, "info" ) ) ) + beInfo = tr_bencDictFind( &meta, "info" ); + if( NULL == beInfo || TYPE_DICT != beInfo->type ) { - tr_err( "Could not find \"info\" dictionary" ); + tr_err( "%s \"info\" dictionary", ( beInfo ? "Invalid" : "Missing" ) ); tr_bencFree( &meta ); free( buf ); return 1; @@ -150,52 +152,54 @@ int tr_metainfoParse( tr_info_t * inf, const char * path, free( buf ); /* Comment info */ - if( ( val = tr_bencDictFind( &meta, "comment.utf-8" ) ) || ( val = tr_bencDictFind( &meta, "comment" ) ) ) + val = tr_bencDictFindFirst( &meta, "comment.utf-8", "comment", NULL ); + if( NULL != val && TYPE_STR == val->type ) { - strcatUTF8( inf->comment, val->val.s.s ); + strcatUTF8( inf->comment, sizeof( inf->comment ), val->val.s.s, 0 ); } /* Creator info */ - if( ( val = tr_bencDictFind( &meta, "created by.utf-8" ) ) || ( val = tr_bencDictFind( &meta, "created by" ) ) ) + tr_bencDictFindFirst( &meta, "created by.utf-8", "created by", NULL ); + if( NULL != val && TYPE_STR == val->type ) { - strcatUTF8( inf->creator, val->val.s.s ); + strcatUTF8( inf->creator, sizeof( inf->creator ), val->val.s.s, 0 ); } /* Date created */ - if( ( val = tr_bencDictFind( &meta, "creation date" ) ) ) + inf->dateCreated = 0; + val = tr_bencDictFind( &meta, "creation date" ); + if( NULL != val && TYPE_INT == val->type ) { inf->dateCreated = val->val.i; } - else - { - inf->dateCreated = 0; - } /* Private torrent */ - if( ( NULL != ( val = tr_bencDictFind( beInfo, "private" ) ) && - TYPE_INT == val->type && val->val.i ) || - ( NULL != ( val = tr_bencDictFind( &meta, "private" ) ) && - TYPE_INT == val->type && val->val.i ) ) + if( tr_bencDictFind( beInfo, "private" ) || + tr_bencDictFind( &meta, "private" ) ) { inf->flags |= TR_FLAG_PRIVATE; } /* Piece length */ - if( !( val = tr_bencDictFind( beInfo, "piece length" ) ) ) + val = tr_bencDictFind( beInfo, "piece length" ); + if( NULL == val || TYPE_INT != val->type ) { - tr_err( "No \"piece length\" entry" ); - tr_bencFree( &meta ); - return 1; + tr_err( "%s \"piece length\" entry", ( val ? "Invalid" : "Missing" ) ); + goto fail; } inf->pieceSize = val->val.i; /* Hashes */ val = tr_bencDictFind( beInfo, "pieces" ); + if( NULL == val || TYPE_STR != val->type ) + { + tr_err( "%s \"pieces\" entry", ( val ? "Invalid" : "Missing" ) ); + goto fail; + } if( val->val.s.i % SHA_DIGEST_LENGTH ) { tr_err( "Invalid \"piece\" string (size is %d)", val->val.s.i ); - tr_bencFree( &meta ); - return 1; + goto fail; } inf->pieceCount = val->val.s.i / SHA_DIGEST_LENGTH; inf->pieces = (uint8_t *) val->val.s.s; /* Ugly, but avoids a memcpy */ @@ -203,42 +207,37 @@ int tr_metainfoParse( tr_info_t * inf, const char * path, /* TODO add more tests so we don't crash on weird files */ + /* get file or top directory name */ + val = tr_bencDictFindFirst( beInfo, "name.utf-8", "name", NULL ); + if( NULL == val || TYPE_STR != val->type ) + { + tr_err( "%s \"name\" string", ( val ? "Invalid" : "Missing" ) ); + goto fail; + } + strcatUTF8( inf->name, sizeof( inf->name ), val->val.s.s, 1 ); inf->totalSize = 0; + if( ( list = tr_bencDictFind( beInfo, "files" ) ) ) { /* Multi-file mode */ - int j; - - val = tr_bencDictFind( beInfo, "name.utf-8" ); - if( NULL == val ) - { - val = tr_bencDictFind( beInfo, "name" ); - } - strcatUTF8( inf->name, val->val.s.s ); - inf->multifile = 1; inf->fileCount = list->val.l.count; inf->files = calloc( inf->fileCount * sizeof( tr_file_t ), 1 ); for( i = 0; i < list->val.l.count; i++ ) { - val = tr_bencDictFind( &list->val.l.vals[i], "path.utf-8" ); - if( NULL == val ) + val = tr_bencDictFindFirst( &list->val.l.vals[i], + "path.utf-8", "path", NULL ); + if( getfile( inf->files[i].name, sizeof( inf->files[i].name ), + inf->name, val ) ) { - val = tr_bencDictFind( &list->val.l.vals[i], "path" ); - } - strcatUTF8( inf->files[i].name, inf->name ); - for( j = 0; j < val->val.l.count; j++ ) - { - strcatUTF8( inf->files[i].name, "/" ); - strcatUTF8( inf->files[i].name, - val->val.l.vals[j].val.s.s ); + tr_err( "%s \"path\" entry", ( val ? "Invalid" : "Missing" ) ); + goto fail; } val = tr_bencDictFind( &list->val.l.vals[i], "length" ); inf->files[i].length = val->val.i; inf->totalSize += val->val.i; } - } else { @@ -247,15 +246,15 @@ int tr_metainfoParse( tr_info_t * inf, const char * path, inf->fileCount = 1; inf->files = calloc( sizeof( tr_file_t ), 1 ); - val = tr_bencDictFind( beInfo, "name.utf-8" ); - if( NULL == val ) - { - val = tr_bencDictFind( beInfo, "name" ); - } - strcatUTF8( inf->files[0].name, val->val.s.s ); - strcatUTF8( inf->name, val->val.s.s ); + strcatUTF8( inf->files[0].name, sizeof( inf->files[0].name), + val->val.s.s, 1 ); val = tr_bencDictFind( beInfo, "length" ); + if( NULL == val || TYPE_INT != val->type ) + { + tr_err( "%s \"length\" entry", ( val ? "Invalid" : "Missing" ) ); + goto fail; + } inf->files[0].length = val->val.i; inf->totalSize += val->val.i; } @@ -264,21 +263,22 @@ int tr_metainfoParse( tr_info_t * inf, const char * path, ( inf->totalSize + inf->pieceSize - 1 ) / inf->pieceSize ) { tr_err( "Size of hashes and files don't match" ); - tr_metainfoFree( inf ); - tr_bencFree( &meta ); - return 1; + goto fail; } /* get announce or announce-list */ if( getannounce( inf, &meta ) ) { - tr_metainfoFree( inf ); - tr_bencFree( &meta ); - return 1; + goto fail; } tr_bencFree( &meta ); return 0; + + fail: + tr_metainfoFree( inf ); + tr_bencFree( &meta ); + return 1; } void tr_metainfoFree( tr_info_t * inf ) @@ -301,6 +301,61 @@ void tr_metainfoFree( tr_info_t * inf ) free( inf->trackerList ); } +static int getfile( char * buf, int size, + const char * prefix, const benc_val_t * name ) +{ + const benc_val_t * dir; + const char ** list; + int ii, jj; + + if( TYPE_LIST != name->type ) + { + return 1; + } + + list = calloc( name->val.l.count, sizeof( list[0] ) ); + if( NULL == list ) + { + return 1; + } + + for( ii = jj = 0; name->val.l.count > ii; ii++ ) + { + if( TYPE_STR != name->val.l.vals[ii].type ) + { + continue; + } + dir = &name->val.l.vals[ii]; + if( 0 == strcmp( "..", dir->val.s.s ) ) + { + if( 0 < jj ) + { + jj--; + } + } + else if( 0 != strcmp( ".", dir->val.s.s ) ) + { + list[jj] = dir->val.s.s; + jj++; + } + } + + if( 0 == jj ) + { + return 1; + } + + strcatUTF8( buf, size, prefix, 0 ); + for( ii = 0; jj > ii; ii++ ) + { + strcatUTF8( buf, size, "/", 0 ); + strcatUTF8( buf, size, list[ii], 1 ); + } + free( list ); + + return 0; +} + static int getannounce( tr_info_t * inf, benc_val_t * meta ) { benc_val_t * val, * subval, * urlval; @@ -474,9 +529,12 @@ void tr_metainfoRemoveSaved( const char * hashString ) **********************************************************************/ #define WANTBYTES( want, got ) \ if( (want) > (got) ) { return; } else { (got) -= (want); } -static void _strcatUTF8( char * s, int len, char * append ) +static void strcatUTF8( char * s, int len, const char * append, int deslash ) { - char * p; + const char * p; + + /* don't overwrite the nul at the end */ + len--; /* Go to the end of the destination string */ while( s[0] ) @@ -488,6 +546,13 @@ static void _strcatUTF8( char * s, int len, char * append ) /* Now start appending, converting on the fly if necessary */ for( p = append; p[0]; ) { + /* skip over / if requested */ + if( deslash && '/' == p[0] ) + { + p++; + continue; + } + if( !( p[0] & 0x80 ) ) { /* ASCII character */