#ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include "mcpu-archive.h" #include "mcpu-asm.h" #include "mcpu-melf.h" #include "mcpu-object.h" #define MCPU_AR_MAGIC "!\n" #define MCPU_AR_MAGIC_SIZE 8U #define MCPU_AR_HEADER_SIZE 60U typedef struct mcpu_ar_raw_header { char name[16]; char date[12]; char uid[6]; char gid[6]; char mode[8]; char size[10]; char fmag[2]; } mcpu_ar_raw_header; static int add_size( size_t a, size_t b, size_t *result ) { if( b > SIZE_MAX - a ) return( -1 ); *result = a + b; return( 0 ); } /* End of add_size() */ static int mul_size( size_t a, size_t b, size_t *result ) { if( a && b > SIZE_MAX / a ) return( -1 ); *result = a * b; return( 0 ); } /* End of mul_size() */ static char * copy_string( const char *s, size_t n ) { char *p = (char *)malloc( n + 1 ); if( !p ) return( NULL ); memcpy( p, s, n ); p[n] = 0; return( p ); } /* End of copy_string() */ const char * mcpu_archive_basename( const char *path ) { const char *p; if( !path ) return( NULL ); p = strrchr( path, '/' ); return( p ? p + 1 : path ); } /* End of mcpu_archive_basename() */ static int valid_member_name( const char *name ) { const unsigned char *p = (const unsigned char *)name; if( !name || !*name || !strcmp( name, "." ) || !strcmp( name, ".." ) ) return( 0 ); while( *p ) { if( *p == '/' || *p == '\n' || *p == '\r' || *p == 0x7f ) return( 0 ); ++p; } return( 1 ); } /* End of valid_member_name() */ void mcpu_archive_member_free( mcpu_archive_member *member ) { size_t i; if( !member ) return; free( member->name ); free( member->data ); for( i = 0; i < member->symbol_count; ++i ) free( member->symbols[i] ); free( member->symbols ); memset( member, 0, sizeof( *member ) ); } /* End of mcpu_archive_member_free() */ void mcpu_archive_init( mcpu_archive *archive ) { if( archive ) memset( archive, 0, sizeof( *archive ) ); } /* End of mcpu_archive_init() */ void mcpu_archive_free( mcpu_archive *archive ) { size_t i; if( !archive ) return; for( i = 0; i < archive->member_count; ++i ) mcpu_archive_member_free( &archive->members[i] ); free( archive->members ); memset( archive, 0, sizeof( *archive ) ); } /* End of mcpu_archive_free() */ static int member_add_symbol( mcpu_archive_member *member, const char *name, char *err, size_t errsz ) { char **q; char *s; q = (char **)realloc( member->symbols, (member->symbol_count + 1) * sizeof( *q ) ); if( !q ) { snprintf( err, errsz, "out of memory" ); return( -1 ); } s = strdup( name ); if( !s ) { snprintf( err, errsz, "out of memory" ); return( -1 ); } member->symbols = q; member->symbols[member->symbol_count++] = s; return( 0 ); } /* End of member_add_symbol() */ static int member_collect_symbols( const char *path, mcpu_archive_member *member, char *err, size_t errsz ) { cpu_asm_object object; size_t i; memset( &object, 0, sizeof( object ) ); if( cpu_object_read( path, &object, err, errsz ) ) return( -1 ); for( i = 0; i < object.symbol_count; ++i ) { const cpu_asm_symbol *s = &object.symbols[i]; if( s->section == CPU_OBJ_SEC_UNDEF || s->binding == CPU_OBJ_BIND_LOCAL ) continue; if( member_add_symbol( member, s->name, err, errsz ) ) { cpu_asm_object_free( &object ); return( -1 ); } } cpu_asm_object_free( &object ); return( 0 ); } /* End of member_collect_symbols() */ static int member_collect_symbols_data( const unsigned char *data, size_t size, mcpu_archive_member *member, char *err, size_t errsz ) { cpu_asm_object object; size_t i; memset( &object, 0, sizeof( object ) ); if( cpu_object_read_data(data, size, &object, err, errsz) ) return( -1 ); for( i = 0; i < object.symbol_count; ++i ) { const cpu_asm_symbol *s = &object.symbols[i]; if( s->section == CPU_OBJ_SEC_UNDEF || s->binding == CPU_OBJ_BIND_LOCAL ) continue; if( member_add_symbol(member, s->name, err, errsz) ) { cpu_asm_object_free( &object ); return( -1 ); } } cpu_asm_object_free( &object ); return( 0 ); } /* End of member_collect_symbols_data() */ int mcpu_archive_member_load( const char *path, mcpu_archive_member *member, char *err, size_t errsz ) { const char *name; unsigned char *data = NULL; size_t size = 0; if( !path || !member ) return( -1 ); memset( member, 0, sizeof( *member ) ); name = mcpu_archive_basename( path ); if( !valid_member_name( name ) ) { snprintf( err, errsz, "invalid archive member name: %s", name ? name : "" ); return( -1 ); } if( cpu_melf_readall( path, &data, &size, err, errsz ) ) return( -1 ); member->name = strdup( name ); member->data = data; member->size = size; if( !member->name ) { snprintf( err, errsz, "out of memory" ); mcpu_archive_member_free( member ); return( -1 ); } if( member_collect_symbols( path, member, err, errsz ) ) { mcpu_archive_member_free( member ); return( -1 ); } return( 0 ); } /* End of mcpu_archive_member_load() */ int mcpu_archive_append( mcpu_archive *archive, mcpu_archive_member *member, char *err, size_t errsz ) { mcpu_archive_member *q; if( !archive || !member ) return( -1 ); q = (mcpu_archive_member *)realloc( archive->members, (archive->member_count + 1) * sizeof( *q ) ); if( !q ) { snprintf( err, errsz, "out of memory" ); return( -1 ); } archive->members = q; archive->members[archive->member_count++] = *member; memset( member, 0, sizeof( *member ) ); return( 0 ); } /* End of mcpu_archive_append() */ static int parse_decimal_field( const char *p, size_t n, size_t *value ) { size_t i = 0; size_t v = 0; int any = 0; while( i < n && p[i] == ' ' ) ++i; while( i < n && p[i] != ' ' ) { unsigned d; if( p[i] < '0' || p[i] > '9' ) return( -1 ); d = (unsigned)(p[i] - '0'); if( v > (SIZE_MAX - d) / 10 ) return( -1 ); v = v * 10 + d; any = 1; ++i; } while( i < n ) { if( p[i] != ' ' ) return( -1 ); ++i; } if( !any ) return( -1 ); *value = v; return( 0 ); } /* End of parse_decimal_field() */ static int raw_name( const mcpu_ar_raw_header *h, char *name, size_t namesz ) { size_t n = sizeof( h->name ); while( n && h->name[n - 1] == ' ' ) --n; if( n + 1 > namesz ) return( -1 ); memcpy( name, h->name, n ); name[n] = 0; return( 0 ); } /* End of raw_name() */ static int long_name_at( const unsigned char *tab, size_t tabsize, size_t off, char **name, char *err, size_t errsz ) { size_t end; if( off >= tabsize ) { snprintf( err, errsz, "bad GNU archive long-name offset" ); return( -1 ); } end = off; while( end < tabsize && tab[end] != '\n' && tab[end] != 0 ) ++end; if( end == tabsize ) { snprintf( err, errsz, "unterminated GNU archive long name" ); return( -1 ); } if( end > off && tab[end - 1] == '/' ) --end; *name = copy_string( (const char *)tab + off, end - off ); if( !*name ) { snprintf( err, errsz, "out of memory" ); return( -1 ); } return( 0 ); } /* End of long_name_at() */ static int read_member_name( const mcpu_ar_raw_header *h, const unsigned char *payload, size_t payload_size, const unsigned char *longtab, size_t longtab_size, char **name, size_t *name_prefix, int *special, char *err, size_t errsz ) { char raw[17]; size_t n; *name = NULL; *name_prefix = 0; *special = 0; if( raw_name( h, raw, sizeof( raw ) ) ) { snprintf( err, errsz, "bad archive member name" ); return( -1 ); } if( !strcmp( raw, "/" ) || !strcmp( raw, "/SYM64/" ) ) { *special = 1; return( 0 ); } if( !strcmp( raw, "//" ) ) { *special = 2; return( 0 ); } if( !strncmp( raw, "#1/", 3 ) ) { if( parse_decimal_field( raw + 3, strlen( raw + 3 ), &n ) || n == 0 || n > payload_size ) { snprintf( err, errsz, "bad BSD archive extended name" ); return( -1 ); } *name = copy_string( (const char *)payload, n ); if( !*name ) { snprintf( err, errsz, "out of memory" ); return( -1 ); } *name_prefix = n; } else if( raw[0] == '/' && raw[1] >= '0' && raw[1] <= '9' ) { size_t off; if( !longtab || parse_decimal_field( raw + 1, strlen( raw + 1 ), &off ) || long_name_at( longtab, longtab_size, off, name, err, errsz ) ) return( -1 ); } else { n = strlen( raw ); if( n && raw[n - 1] == '/' ) --n; *name = copy_string( raw, n ); if( !*name ) { snprintf( err, errsz, "out of memory" ); return( -1 ); } } if( !valid_member_name( *name ) ) { snprintf( err, errsz, "invalid archive member name: %s", *name ); free( *name ); *name = NULL; return( -1 ); } return( 0 ); } /* End of read_member_name() */ int mcpu_archive_read( const char *path, mcpu_archive *archive, char *err, size_t errsz ) { unsigned char *file = NULL; size_t file_size = 0; size_t off; const unsigned char *longtab = NULL; size_t longtab_size = 0; int rc = -1; if( !path || !archive ) return( -1 ); mcpu_archive_init( archive ); if( cpu_melf_readall( path, &file, &file_size, err, errsz ) ) return( -1 ); if( file_size < MCPU_AR_MAGIC_SIZE || memcmp( file, MCPU_AR_MAGIC, MCPU_AR_MAGIC_SIZE ) ) { snprintf( err, errsz, "not a UNIX archive" ); goto out; } off = MCPU_AR_MAGIC_SIZE; while( off < file_size ) { const mcpu_ar_raw_header *h; size_t stored_size; size_t next; const unsigned char *payload; char *name = NULL; size_t name_prefix = 0; int special = 0; mcpu_archive_member member; memset( &member, 0, sizeof( member ) ); if( file_size - off < MCPU_AR_HEADER_SIZE ) { snprintf( err, errsz, "truncated archive member header" ); goto out; } h = (const mcpu_ar_raw_header *)(file + off); if( h->fmag[0] != '`' || h->fmag[1] != '\n' || parse_decimal_field( h->size, sizeof( h->size ), &stored_size ) ) { snprintf( err, errsz, "bad archive member header" ); goto out; } if( stored_size > file_size - off - MCPU_AR_HEADER_SIZE ) { snprintf( err, errsz, "truncated archive member" ); goto out; } payload = file + off + MCPU_AR_HEADER_SIZE; if( read_member_name( h, payload, stored_size, longtab, longtab_size, &name, &name_prefix, &special, err, errsz ) ) goto out; if( special == 1 ) archive->had_symbol_index = 1; else if( special == 2 ) { longtab = payload; longtab_size = stored_size; } else { if( name_prefix > stored_size ) { free( name ); snprintf( err, errsz, "bad archive extended member size" ); goto out; } member.name = name; member.size = stored_size - name_prefix; member.data = (unsigned char *)malloc( member.size ? member.size : 1 ); if( !member.data ) { snprintf( err, errsz, "out of memory" ); mcpu_archive_member_free( &member ); goto out; } if( member.size ) memcpy( member.data, payload + name_prefix, member.size ); if( member_collect_symbols_data( member.data, member.size, &member, err, errsz ) || mcpu_archive_append( archive, &member, err, errsz ) ) { mcpu_archive_member_free( &member ); goto out; } } if( add_size( off, MCPU_AR_HEADER_SIZE, &next ) || add_size( next, stored_size, &next ) ) { snprintf( err, errsz, "archive offset overflow" ); goto out; } if( stored_size & 1 ) { if( next >= file_size || file[next] != '\n' ) { snprintf( err, errsz, "bad archive member padding" ); goto out; } ++next; } off = next; } rc = 0; out: free( file ); if( rc ) mcpu_archive_free( archive ); return( rc ); } /* End of mcpu_archive_read() */ static int short_member_name( const char *name ) { const unsigned char *p = (const unsigned char *)name; size_t n = strlen( name ); if( n == 0 || n > 15 ) return( 0 ); while( *p ) { if( *p == ' ' || *p == '/' || *p == '\t' ) return( 0 ); ++p; } return( 1 ); } /* End of short_member_name() */ static int member_stored_size( const mcpu_archive_member *member, size_t *size ) { size_t n = short_member_name( member->name ) ? 0 : strlen( member->name ); return( add_size( n, member->size, size ) ); } /* End of member_stored_size() */ static int format_field( char *dst, size_t n, const char *text ) { size_t len = strlen( text ); if( len > n ) return( -1 ); memcpy( dst, text, len ); memset( dst + len, ' ', n - len ); return( 0 ); } /* End of format_field() */ static int format_size_field( char *dst, size_t n, size_t value ) { char text[32]; snprintf( text, sizeof( text ), "%zu", value ); return( format_field( dst, n, text ) ); } /* End of format_size_field() */ static int make_header( mcpu_ar_raw_header *h, const char *name, size_t stored_size, int special ) { char namebuf[32]; memset( h, ' ', sizeof( *h ) ); if( special ) { if( format_field( h->name, sizeof( h->name ), name ) ) return( -1 ); } else if( short_member_name( name ) ) { snprintf( namebuf, sizeof( namebuf ), "%s/", name ); if( format_field( h->name, sizeof( h->name ), namebuf ) ) return( -1 ); } else { snprintf( namebuf, sizeof( namebuf ), "#1/%zu", strlen( name ) ); if( format_field( h->name, sizeof( h->name ), namebuf ) ) return( -1 ); } if( format_field( h->date, sizeof( h->date ), "0" ) || format_field( h->uid, sizeof( h->uid ), "0" ) || format_field( h->gid, sizeof( h->gid ), "0" ) || format_field( h->mode, sizeof( h->mode ), special ? "0" : "644" ) || format_size_field( h->size, sizeof( h->size ), stored_size ) ) return( -1 ); h->fmag[0] = '`'; h->fmag[1] = '\n'; return( 0 ); } /* End of make_header() */ static int write_bytes( FILE *fp, const void *data, size_t size ) { return( size && fwrite( data, 1, size, fp ) != size ? -1 : 0 ); } /* End of write_bytes() */ static void put_be32( unsigned char *p, uint32_t v ) { p[0] = (unsigned char)(v >> 24); p[1] = (unsigned char)(v >> 16); p[2] = (unsigned char)(v >> 8); p[3] = (unsigned char)v; } /* End of put_be32() */ static int symbol_index_size( const mcpu_archive *archive, size_t *count, size_t *payload_size, char *err, size_t errsz ) { size_t i, j; size_t c = 0; size_t names = 0; size_t offsets; size_t total; for( i = 0; i < archive->member_count; ++i ) { for( j = 0; j < archive->members[i].symbol_count; ++j ) { size_t n = strlen( archive->members[i].symbols[j] ) + 1; if( add_size( names, n, &names ) || c == SIZE_MAX ) { snprintf( err, errsz, "archive symbol index is too large" ); return( -1 ); } ++c; } } if( c > UINT32_MAX || mul_size( c, 4, &offsets ) || add_size( 4, offsets, &total ) || add_size( total, names, &total ) ) { snprintf( err, errsz, "archive symbol index is too large" ); return( -1 ); } *count = c; *payload_size = total; return( 0 ); } /* End of symbol_index_size() */ static int compute_member_offsets( const mcpu_archive *archive, int write_index, size_t index_size, uint32_t **offsets, char *err, size_t errsz ) { uint32_t *v; size_t off = MCPU_AR_MAGIC_SIZE; size_t i; v = (uint32_t *)calloc( archive->member_count ? archive->member_count : 1, sizeof( *v ) ); if( !v ) { snprintf( err, errsz, "out of memory" ); return( -1 ); } if( write_index ) { if( add_size( off, MCPU_AR_HEADER_SIZE, &off ) || add_size( off, index_size, &off ) ) { snprintf( err, errsz, "archive is too large" ); free( v ); return( -1 ); } if( index_size & 1 ) { if( off == SIZE_MAX ) { snprintf( err, errsz, "archive is too large" ); free( v ); return( -1 ); } ++off; } } for( i = 0; i < archive->member_count; ++i ) { size_t stored; if( off > UINT32_MAX || member_stored_size( &archive->members[i], &stored ) ) { snprintf( err, errsz, "archive is too large for a 32-bit symbol index" ); free( v ); return( -1 ); } v[i] = (uint32_t)off; if( add_size( off, MCPU_AR_HEADER_SIZE, &off ) || add_size( off, stored, &off ) ) { snprintf( err, errsz, "archive is too large" ); free( v ); return( -1 ); } if( stored & 1 ) { if( off == SIZE_MAX ) { snprintf( err, errsz, "archive is too large" ); free( v ); return( -1 ); } ++off; } } *offsets = v; return( 0 ); } /* End of compute_member_offsets() */ static int write_armap( FILE *fp, const mcpu_archive *archive, const uint32_t *member_offsets, size_t count, size_t payload_size, char *err, size_t errsz ) { mcpu_ar_raw_header h; unsigned char *payload; size_t pos; size_t i, j, k = 0; if( make_header( &h, "/", payload_size, 1 ) ) { snprintf( err, errsz, "archive symbol index header is too large" ); return( -1 ); } payload = (unsigned char *)malloc( payload_size ? payload_size : 1 ); if( !payload ) { snprintf( err, errsz, "out of memory" ); return( -1 ); } put_be32( payload, (uint32_t)count ); pos = 4; for( i = 0; i < archive->member_count; ++i ) for( j = 0; j < archive->members[i].symbol_count; ++j ) { put_be32( payload + pos, member_offsets[i] ); pos += 4; ++k; } for( i = 0; i < archive->member_count; ++i ) for( j = 0; j < archive->members[i].symbol_count; ++j ) { size_t n = strlen( archive->members[i].symbols[j] ) + 1; memcpy( payload + pos, archive->members[i].symbols[j], n ); pos += n; } if( k != count || pos != payload_size || write_bytes( fp, &h, sizeof( h ) ) || write_bytes( fp, payload, payload_size ) || (payload_size & 1 && fputc( '\n', fp ) == EOF) ) { snprintf( err, errsz, "cannot write archive symbol index: %s", strerror( errno ? errno : EIO ) ); free( payload ); return( -1 ); } free( payload ); return( 0 ); } /* End of write_symbol_index() */ static int write_member( FILE *fp, const mcpu_archive_member *member, char *err, size_t errsz ) { mcpu_ar_raw_header h; size_t stored; int shortname = short_member_name( member->name ); if( member_stored_size( member, &stored ) || make_header( &h, member->name, stored, 0 ) ) { snprintf( err, errsz, "archive member is too large: %s", member->name ); return( -1 ); } if( write_bytes( fp, &h, sizeof( h ) ) || (!shortname && write_bytes( fp, member->name, strlen( member->name ) )) || write_bytes( fp, member->data, member->size ) || (stored & 1 && fputc( '\n', fp ) == EOF) ) { snprintf( err, errsz, "cannot write archive member %s: %s", member->name, strerror( errno ? errno : EIO ) ); return( -1 ); } return( 0 ); } /* End of write_member() */ int mcpu_archive_write( const char *path, const mcpu_archive *archive, int write_symbol_index, char *err, size_t errsz ) { char *tmp; size_t tmpsz; int fd = -1; FILE *fp = NULL; size_t symbol_count = 0; size_t index_size = 0; uint32_t *member_offsets = NULL; size_t i; int rc = -1; if( !path || !archive ) return( -1 ); if( write_symbol_index && symbol_index_size( archive, &symbol_count, &index_size, err, errsz ) ) return( -1 ); if( write_symbol_index && compute_member_offsets( archive, 1, index_size, &member_offsets, err, errsz ) ) return( -1 ); if( add_size( strlen( path ), sizeof( ".tmp.XXXXXX" ), &tmpsz ) ) { snprintf( err, errsz, "archive pathname is too long" ); goto out; } tmp = (char *)malloc( tmpsz ); if( !tmp ) { snprintf( err, errsz, "out of memory" ); goto out; } snprintf( tmp, tmpsz, "%s.tmp.XXXXXX", path ); fd = mkstemp( tmp ); if( fd < 0 ) { snprintf( err, errsz, "cannot create archive temporary file: %s", strerror( errno ) ); free( tmp ); goto out; } fp = fdopen( fd, "wb" ); if( !fp ) { snprintf( err, errsz, "cannot open archive temporary stream: %s", strerror( errno ) ); close( fd ); fd = -1; unlink( tmp ); free( tmp ); goto out; } fd = -1; if( write_bytes( fp, MCPU_AR_MAGIC, MCPU_AR_MAGIC_SIZE ) || (write_symbol_index && write_armap( fp, archive, member_offsets, symbol_count, index_size, err, errsz )) ) goto write_fail; for( i = 0; i < archive->member_count; ++i ) if( write_member( fp, &archive->members[i], err, errsz ) ) goto write_fail; if( fflush( fp ) || fsync( fileno( fp ) ) || fclose( fp ) ) { fp = NULL; snprintf( err, errsz, "cannot finalize archive: %s", strerror( errno ) ); unlink( tmp ); free( tmp ); goto out; } fp = NULL; if( rename( tmp, path ) ) { snprintf( err, errsz, "cannot install archive %s: %s", path, strerror( errno ) ); unlink( tmp ); free( tmp ); goto out; } free( tmp ); rc = 0; goto out; write_fail: if( !err[0] ) snprintf( err, errsz, "cannot write archive: %s", strerror( errno ) ); fclose( fp ); fp = NULL; unlink( tmp ); free( tmp ); out: if( fp ) fclose( fp ); if( fd >= 0 ) close( fd ); free( member_offsets ); return( rc ); } /* End of mcpu_archive_write() */