Skip to content

Instantly share code, notes, and snippets.

@aakropotkin
Last active January 28, 2021 07:10
Show Gist options
  • Save aakropotkin/92a14835a01cae8c505c3db5e339e011 to your computer and use it in GitHub Desktop.
Save aakropotkin/92a14835a01cae8c505c3db5e339e011 to your computer and use it in GitHub Desktop.
List all ELF files recursively from a set of directories

Note:

Parts of this snippet were taken from the Open Source utility "pax-utils" https://pax.grsecurity.net/, this gist not intended for distribution or packaging into commercial software without the consent of the original authors. These are the portions related to processing AR archives, which were slighly modified to make them usable outside of the scanelf utility.

This clipping is intented for individual use.

The code which was expropriated from grsecurity's open source project is under GNU Public License V2 of 1991. This is a gist so I'm not going to dump the giant license here, but be a good person, etc.

/* -*- mode: c; -*- */
/* ========================================================================== */
#include "util.h"
#include <stdio.h>
#include <errno.h>
#include <assert.h>
#include <link.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fts.h>
#include <limits.h>
#include <unistd.h>
#include <fcntl.h>
/* -------------------------------------------------------------------------- */
bool
elfp( const char * fname )
{
ElfW(Ehdr) header;
FILE * f = fopen( fname, "rb" );
if ( f == NULL ) return false;
fread( & header, sizeof( header ), 1, f );
fclose( f );
return ( memcmp( header.e_ident, ELFMAG, SELFMAG ) == 0 );
}
/* -------------------------------------------------------------------------- */
#define AR_MAGIC "!<arch>"
#define AR_MAGIC_SIZE ( sizeof( AR_MAGIC ) - 1 )
bool
arp( const char * fname )
{
char buf[AR_MAGIC_SIZE];
FILE * f = fopen( fname, "rb" );
fread( & buf, AR_MAGIC_SIZE, 1, f );
fclose( f );
return ( memcmp( buf, AR_MAGIC, AR_MAGIC_SIZE ) == 0 );
}
typedef struct {
char name[PATH_MAX];
time_t date;
uid_t uid;
gid_t gid;
mode_t mode;
off_t size;
union {
char raw[60];
struct {
char name[16];
char date[12];
char uid[6];
char gid[6];
char mode[8];
char size[10];
char magic[2];
} formatted;
} buf;
} ar_member_t;
typedef struct {
int fd;
const char * fname;
size_t skip;
char * extfn;
bool verbose;
} ar_handle_t;
static bool
ar_open_fd( const char * filename, int fd, ar_handle_t * handle, bool verbose )
{
char buf[AR_MAGIC_SIZE];
if ( ( read( fd, buf, AR_MAGIC_SIZE ) != AR_MAGIC_SIZE ) ||
( strncmp( buf, AR_MAGIC, AR_MAGIC_SIZE ) != 0 )
) return false;
handle->fname = filename;
handle->fd = fd;
handle->skip = 0;
handle->extfn = NULL;
handle->verbose = verbose;
return true;
}
static bool
ar_open( const char * filename, ar_handle_t * handle, bool verbose )
{
int fd = -1;
if ( ( fd = open( filename, O_RDONLY ) ) == -1 )
{
fprintf( stderr, "%s: could not open\n", filename);
}
if ( ! ar_open_fd( filename, fd, handle, verbose ) )
{
close( fd );
return false;
}
return true;
}
static bool
ar_next( ar_handle_t * ar, ar_member_t * member )
{
char * s = NULL;
ssize_t len = 0;
if ( ar->skip && lseek( ar->fd, ar->skip, SEEK_CUR ) == -1 )
{
close_and_ret:
free( ar->extfn );
close( ar->fd );
ar->extfn = NULL;
ar->fd = -1;
return false;
}
if ( read( ar->fd, member->buf.raw,
sizeof( member->buf.raw )
) != sizeof( member->buf.raw )
)
{
goto close_and_ret;
}
/* ar header starts on an even byte (2 byte aligned)
* '\n' is used for padding */
if ( member->buf.raw[0] == '\n' )
{
memmove( member->buf.raw, member->buf.raw + 1, 59 );
if ( read( ar->fd, member->buf.raw + 59, 1 ) != 1 ) goto close_and_ret;
}
if ( ( member->buf.formatted.magic[0] != '`' ) ||
( member->buf.formatted.magic[1] != '\n' )
)
{
/* When dealing with corrupt or random embedded cross-compilers, they may
* be abusing the archive format; only complain when in verbose mode. */
if ( ar->verbose )
{
fprintf( stderr, "%s: invalid ar entry\n", ar->fname );
}
goto close_and_ret;
}
if ( ( member->buf.formatted.name[0] == '/' ) &&
( member->buf.formatted.name[1] == '/' )
)
{
if ( ar->extfn != NULL )
{
fprintf( stderr,
"%s: Duplicate GNU extended filename section\n",
ar->fname
);
goto close_and_ret;
}
len = atoi( member->buf.formatted.size );
ar->extfn = malloc( sizeof( char ) * ( len + 1 ) );
assert( ar->extfn != NULL ); /* FIXME */
if ( read( ar->fd, ar->extfn, len ) != len ) goto close_and_ret;
ar->extfn[len--] = '\0';
for (; len > 0; len--)
{
if ( ar->extfn[len] == '\n' ) ar->extfn[len] = '\0';
}
ar->skip = 0;
return ar_next( ar, member );
}
s = member->buf.formatted.name;
if ( ( s[0] == '#' ) && ( s[1] == '1' ) && ( s[2] == '/' ) )
{
/* BSD extended filename, always in use on Darwin */
len = atoi( s + 3 );
if ( len <= (ssize_t) sizeof( member->buf.formatted.name ) )
{
if ( read( ar->fd, member->buf.formatted.name, len ) != len )
{
goto close_and_ret;
}
}
else
{
s = alloca( sizeof( char ) * len + 1 );
if ( read( ar->fd, s, len ) != len ) goto close_and_ret;
s[len] = '\0';
}
}
else if ( ( s[0] == '/' ) && ( s[1] >= '0' ) && ( s[1] <= '9' ) )
{
/* GNU extended filename */
if ( ar->extfn == NULL )
{
fprintf( stderr,
"%s: GNU extended filename without special data section\n",
ar->fname
);
goto close_and_ret;
}
s = ar->extfn + atoi( s + 1 );
}
snprintf( member->name, sizeof( member->name ), "%s:%s", ar->fname, s );
member->name[sizeof( member->name ) - 1] = '\0';
if ( ( s = strchr( member->name + strlen( ar->fname ), '/' ) ) != NULL )
{
*s = '\0';
}
member->date = atoi( member->buf.formatted.date );
member->uid = atoi( member->buf.formatted.uid );
member->gid = atoi( member->buf.formatted.gid );
member->mode = strtol( member->buf.formatted.mode, NULL, 8 );
member->size = atoi( member->buf.formatted.size );
ar->skip = member->size - len;
return true;
}
bool
arelfp( const char * fname )
{
ElfW(Ehdr) header;
ar_handle_t handle;
ar_member_t member;
struct stat st;
char * ar_buffer = NULL;
if ( ! ar_open( fname, & handle, true ) ) return false;
fstat( handle.fd, & st );
ar_buffer = mmap( 0, st.st_size, PROT_READ, MAP_PRIVATE, handle.fd, 0 );
while ( ar_next( & handle, & member ) )
{
off_t cur_pos = lseek( handle.fd, 0, SEEK_CUR );
assert( cur_pos != -1 ); /* FIXME */
if ( member.size < sizeof( header ) ) continue;
memcpy( & header, ( ar_buffer + cur_pos ), sizeof( header ) );
if ( memcmp( header.e_ident, ELFMAG, SELFMAG ) == 0 )
{
munmap( ar_buffer, st.st_size );
free( handle.extfn );
handle.extfn = NULL;
close( handle.fd );
handle.fd = -1;
return true;
}
}
munmap( ar_buffer, st.st_size );
return false;
}
/* -------------------------------------------------------------------------- */
/** Used to track inodes which have been visited on a device. */
typedef struct _dev_lst {
dev_t dev;
ino_t * nodes;
size_t ncnt;
size_t ncap;
struct _dev_lst * nxt;
} dev_lst;
#define DEV_LST_DEFAULT_SIZE 256
/**
* Mark the file corresponding to the Device/Inode indicated as "visited".
* Return true if the given file had already been visited previously.
*/
bool dev_lst_mark( dev_lst *, dev_t, ino_t ) __attribute__(( nonnull ));
void dev_lst_realloc( dev_lst *, size_t ) __attribute__(( nonnull ));
void dev_lst_free( dev_lst * ) __attribute__(( nonnull ));
void
dev_lst_realloc( dev_lst * elem, size_t nsize )
{
if ( ( elem->nodes == NULL ) || ( elem->ncnt == 0 ) )
{
elem->nodes = malloc( sizeof( ino_t ) * nsize );
}
else
{
elem->nodes = realloc( elem->nodes, sizeof( ino_t ) * nsize );
}
assert( elem->nodes != NULL );
elem->ncap = nsize;
}
bool
dev_lst_mark( dev_lst * dlst, dev_t dev, ino_t ino )
{
/* Find the device corresponding to `dev' if it exists */
while ( ( dlst != NULL ) && ( dlst->nxt != NULL ) && ( dlst->dev != dev ) )
{
dlst = dlst->nxt;
}
if ( dlst->dev == dev ) /* Device was found */
{
for ( size_t i = 0; i < dlst->ncnt; i++ ) /* Find the inode */
{
if ( dlst->nodes[i] == ino ) return true;
}
/* Inode not found, add it. Doubling size of inode list if needed. */
if ( dlst->ncap <= ( dlst->ncnt + 1 ) )
{
dev_lst_realloc( dlst, 2 * dlst->ncap );
}
dlst->nodes[dlst->ncnt++] = ino;
}
else /* A new device was found, add it to the device list */
{
assert( dlst->nxt == NULL );
dlst->nxt = malloc( sizeof( dev_lst ) );
assert( dlst->nxt != NULL );
dlst = dlst->nxt;
dlst->dev = dev;
dlst->nodes = NULL;
dlst->ncnt = 0;
dlst->nxt = NULL;
dev_lst_realloc( dlst, DEV_LST_DEFAULT_SIZE );
dlst->nodes[0] = ino;
dlst->ncnt++;
}
return false;
}
void
dev_lst_free( dev_lst * dlst )
{
free( dlst->nodes );
dlst->nodes = NULL;
if ( dlst->nxt != NULL )
{
dev_lst_free( dlst->nxt );
dlst->nxt = NULL;
}
free( dlst );
}
/* -------------------------------------------------------------------------- */
void
do_print_elf_objects( const char * fpath, void * _unused )
{
if ( elfp( fpath ) || arelfp( fpath ) )
//if ( arelfp( fpath ) )
{
/* We have a hit! */
printf( "%s\n", fpath );
}
}
void
print_elfs_recur( char * const * paths, int pathc )
{
map_files_recur( paths, pathc, do_print_elf_objects, NULL );
}
/* -------------------------------------------------------------------------- */
void
map_files_recur( char * const * paths, int pathc, do_file_fn fn, void * aux )
{
char ** abspaths = NULL;
char * fpath = NULL;
FTS * fs = NULL;
FTSENT * child = NULL;
FTSENT * parent = NULL;
dev_lst * visited = NULL;
/* Initialize the marker list */
visited = malloc( sizeof( dev_lst ) );
assert( visited != NULL );
visited->nodes = NULL;
visited->ncnt = 0;
visited->nxt = NULL;
dev_lst_realloc( visited, DEV_LST_DEFAULT_SIZE );
/* Convert any relative paths to absolute paths */
abspaths = malloc( sizeof( char * ) * pathc );
assert( abspaths != NULL );
for ( int i = 0; i < pathc; i++ )
{
abspaths[i] = realpath( paths[i], NULL );
assert( abspaths[i] != NULL );
}
fs = fts_open( abspaths, FTS_LOGICAL|FTS_COMFOLLOW, NULL );
assert( fs != NULL );
while ( ( parent = fts_read( fs ) ) != NULL )
{
errno = 0;
child = fts_children( fs, 0 );
if ( errno != 0 ) perror( "fts_children" );
/* The first file opened is used to assign the first device in the
* marker list */
if ( visited->ncnt <= 0 ) visited->dev = child->fts_statp->st_dev;
while ( child != NULL )
{
if ( dev_lst_mark( visited,
child->fts_statp->st_dev,
child->fts_statp->st_ino
)
)
{
child = child->fts_link;
continue;
}
/* Paste together the path and filenames */
asprintf( & fpath, "%s%s", child->fts_path, child->fts_name );
fn( fpath, aux ); /* Apply the provided function */
free( fpath );
fpath = NULL;
child = child->fts_link;
}
}
/* Cleanup */
parent = NULL;
child = NULL;
for ( int i = 0; i < pathc; i++ ) free( abspaths[i] );
free( abspaths );
abspaths = NULL;
dev_lst_free( visited );
visited = NULL;
fts_close( fs );
fs = NULL;
}
/* -------------------------------------------------------------------------- */
struct fn_aux_s { do_file_fn fn; void * aux; };
static void
do_to_elfs( const char * fname, void * aux )
{
if ( elfp( fname ) )
{
( (struct fn_aux_s *) aux )->fn( fname,
( (struct fn_aux_s *) aux )->aux
);
}
else if ( arelfp( fname ) )
{
/* FIXME: Split AR members into temporary files and and apply */
}
}
void
map_elfs_recur( char * const * paths, int pathc, do_file_fn fn, void * aux )
{
struct fn_aux_s user_args = { fn, aux };
map_files_recur( paths, pathc, do_to_elfs, & user_args );
}
/* -------------------------------------------------------------------------- */
int
main( int argc, char * argv[], char ** envp )
{
print_elfs_recur( argv + 1, argc - 1 );
return EXIT_SUCCESS;
}
/* -------------------------------------------------------------------------- */
/* ========================================================================== */
/* vim: set filetype=c : */
/* -*- mode: c; -*- */
#ifndef _LIBSCRIPTS_UTIL_H
#define _LIBSCRIPTS_UTIL_H
/* ========================================================================== */
#define _GNU_SOURCE
#include <elf.h>
#include <stdlib.h>
#include <stdbool.h>
/* -------------------------------------------------------------------------- */
/** Detect if the file at path `fname' has ELF format. */
bool elfp( const char * fname ) __attribute__(( nonnull ));
/** Detect if the file at path `fname' is AR archive. */
bool arp( const char * fname ) __attribute__(( nonnull ));
/** Detect if the file at path `fname' is AR archive containing ELF data. */
bool arelfp( const char * fname ) __attribute__(( nonnull ));
/* -------------------------------------------------------------------------- */
/**
* Lambda which may be applied to filepaths using `map_files_recur'.
* FIXME: Change to accept file descriptor instead to allow members of AR
* archives to be mapped over as if they were regular files.
* The alternative is to temporarily extract members, which is
* less efficient.
*/
typedef void (*do_file_fn)( const char * fname, void * aux );
/** Applies a `do_file_fn' to all files. */
void map_files_recur( char * const *, int, do_file_fn, void * aux )
__attribute__(( nonnull( 1, 3 ) ));
/** Applies `do_file_fn' to ELF files only. */
void map_elfs_recur( char * const *, int, do_file_fn, void * aux )
__attribute__(( nonnull( 1, 3 ) ));
/* -------------------------------------------------------------------------- */
void do_print_elf_objects( const char * fpath, void * _unused )
__attribute__(( nonnull( 1 ) ));
/**
* Print all ELF files within the directories and subdirectories name in the
* array `paths'.
* `pathc' indicates the number of elements in `paths'.
*/
void print_elfs_recur( char * const *, int ) __attribute__(( nonnull ));
/* -------------------------------------------------------------------------- */
/* ========================================================================== */
#endif /* util.h */
/* vim: set filetype=c : */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment