Fix archive file io plugin (#3417)

* Fix loading .lib/.a files
* Add windows lib test for ar:// uri
This commit is contained in:
Giovanni 2023-03-10 08:13:26 +08:00 committed by GitHub
parent 722c6024fe
commit eb3f7f80c5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 225 additions and 81 deletions

View file

@ -13,5 +13,6 @@
#define IS_WHITESPACE(x) ((x) == ' ' || (x) == '\t')
#define IS_UPPER(c) ((c) >= 'A' && (c) <= 'Z')
#define IS_LOWER(c) ((c) >= 'a' && (c) <= 'z')
#define IS_ALPHANUM(c) (IS_DIGIT(c) || IS_UPPER(c) || IS_LOWER(c))
#endif // RZ_STR_UTIL_H

View file

@ -1,10 +1,23 @@
// SPDX-FileCopyrightText: 2023 deroad <wargio@libero.it>
// SPDX-FileCopyrightText: 2017 xarkes <antide.petit@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#include <stdio.h>
#include <sys/stat.h>
#include "ar.h"
#define AR_MAGIC_HEADER "!<arch>\n"
#define AR_FILE_HEADER_END "`\n"
#define IS_PATH(c) (IS_ALPHANUM(c) || (c == '/') || (c == '\\') || (c == '.'))
#define AR_MAGIC_HEADER "!<arch>\n"
#define AR_ENTRY_DELIMITER "`\n"
#define AR_ENTRY_NAME_LEN 16
#define AR_ENTRY_DATE_LEN 12
#define AR_ENTRY_UID_LEN 6
#define AR_ENTRY_GID_LEN 6
#define AR_ENTRY_MODE_LEN 8
#define AR_ENTRY_SIZE_LEN 10
#define AR_ENTRY_DELIM_LEN 2
#define AR_ENTRY_MODE_OFF (AR_ENTRY_DATE_LEN + AR_ENTRY_UID_LEN + AR_ENTRY_GID_LEN)
#define AR_ENTRY_MODE_INVALID UT32_MAX
typedef struct Filetable {
char *data;
@ -61,112 +74,174 @@ static char *name_from_table(ut64 off, filetable *tbl) {
if (i == off) {
return NULL;
}
return rz_str_newlen(buf + off, i - off - 1);
return rz_str_newlen(buf + off, i - off);
}
#define VERIFY_AR_NUM_FIELD(x, s) \
x[sizeof(x) - 1] = '\0'; \
rz_str_trim_tail(x); \
if (x[0] != '\0' && (x[0] == '-' || !rz_str_isnumber(x))) { \
RZ_LOG_ERROR("ar: Malformed AR: bad %s in header at offset 0x%" PFMT64x "\n", s, h_off); \
return -1; \
static ut64 pow8[] = {
01, 010, 0100, 01000, 010000, 0100000, 01000000, 010000000, 0100000000
};
static ut32 parse_st_mode(const char *s_mode) {
if (!s_mode[0]) {
return 0;
}
ut32 decimal = 0;
ut32 result = 0;
ut32 octal = atoi(s_mode);
if (octal > 177777) {
// atoi will convert 177777oct into 177777dec, so we test for this.
return AR_ENTRY_MODE_INVALID;
}
/* -1 error, 0 end, 1 contnue */
static int ar_parse_header(RzArFp *arf, filetable *tbl, ut64 arsize) {
while (octal > 0) {
ut32 y = octal % 10;
octal /= 10;
result += y * pow8[decimal];
++decimal;
if (decimal >= RZ_ARRAY_SIZE(pow8)) {
// this should never happen.
return AR_ENTRY_MODE_INVALID;
}
}
return result;
}
static bool ar_read_entry(RzBuffer *buffer, char *e_name, ut64 *e_size, ut32 *e_mode) {
st64 cursor = rz_buf_tell(buffer);
if (cursor >= rz_buf_size(buffer)) {
return false;
}
// always ensure the strings are null terminated.
char s_mode[AR_ENTRY_MODE_LEN + 1] = { 0 };
char s_size[AR_ENTRY_SIZE_LEN + 1] = { 0 };
char s_delimiter[AR_ENTRY_DELIM_LEN] = { 0 };
if (rz_buf_read(buffer, (ut8 *)e_name, AR_ENTRY_NAME_LEN) != AR_ENTRY_NAME_LEN) {
cursor = rz_buf_tell(buffer);
RZ_LOG_ERROR("ar: expected entry name at 0x%" PFMT64x " but couldn't read.\n", cursor);
return false;
}
cursor = rz_buf_tell(buffer);
if (rz_buf_seek(buffer, AR_ENTRY_MODE_OFF, RZ_BUF_CUR) <= cursor) {
cursor += AR_ENTRY_MODE_OFF;
RZ_LOG_ERROR("ar: failed to seek at 0x%" PFMT64x ".\n", cursor);
return false;
}
if (rz_buf_read(buffer, (ut8 *)s_mode, AR_ENTRY_MODE_LEN) != AR_ENTRY_MODE_LEN) {
cursor = rz_buf_tell(buffer);
RZ_LOG_ERROR("ar: expected entry mode at 0x%" PFMT64x " but couldn't read.\n", cursor);
return false;
}
if (rz_buf_read(buffer, (ut8 *)s_size, AR_ENTRY_SIZE_LEN) != AR_ENTRY_SIZE_LEN) {
cursor = rz_buf_tell(buffer);
RZ_LOG_ERROR("ar: expected entry size at 0x%" PFMT64x " but couldn't read.\n", cursor);
return false;
}
if (rz_buf_read(buffer, (ut8 *)s_delimiter, sizeof(s_delimiter)) != sizeof(s_delimiter) ||
s_delimiter[0] != '`' || s_delimiter[1] != '\n') {
cursor = rz_buf_tell(buffer);
RZ_LOG_ERROR("ar: expected entry delimiter at 0x%" PFMT64x " but it wasn't found.\n", cursor);
return false;
}
rz_str_trim_tail(e_name);
rz_str_trim_tail(s_mode);
rz_str_trim_tail(s_size);
*e_size = s_size[0] ? atol(s_size) : 0;
*e_mode = parse_st_mode(s_mode);
return true;
}
static void ar_sanitize_name(RzArFp *arf) {
bool trim_end = true;
st64 len = strlen(arf->name);
if (len < 1) {
return;
}
// cleanup path which could be present in names
for (st64 i = len - 1; i >= 0; i--) {
if (trim_end && (arf->name[i] == '\\' || arf->name[i] == '/')) {
// files shall never end with path delimeters.
arf->name[i] = '_';
} else if (arf->name[i] == '\\') {
// we use URI to access a single file
// thus the path needs to be unix only
arf->name[i] = '/';
} else if (!IS_PATH(arf->name[i])) {
trim_end = false;
arf->name[i] = '_';
}
}
}
/* -1 error, 0 end, 1 continue */
static int ar_parse_entry(RzArFp *arf, filetable *tbl, ut64 arsize) {
rz_return_val_if_fail(arf && arf->buf && tbl, -1);
RzBuffer *b = arf->buf;
// always ensure the strings are null terminated.
char e_name[AR_ENTRY_NAME_LEN + 1] = { 0 };
ut64 e_size = 0;
ut32 e_mode = 0;
ut64 h_off = rz_buf_tell(b);
if (h_off % 2 == 1) {
ut64 e_offset = rz_buf_tell(b);
if (e_offset % 2 == 1) {
// headers start at even offset
ut8 tmp[1];
if (rz_buf_read(b, tmp, 1) != 1 || tmp[0] != '\n') {
return -1;
}
h_off++;
e_offset++;
}
struct header {
char name[16];
char timestamp[12];
char oid[6];
char gid[6];
char mode[8];
char size[10];
char end[2];
} h;
int r = rz_buf_read(b, (ut8 *)&h, sizeof(h));
if (r != sizeof(h)) {
if (r == 0) {
return 0; // no more file
}
if (r < 0) {
RZ_LOG_ERROR("ar: io error\n");
} else {
RZ_LOG_ERROR("ar: Invalid file length\n");
}
if (!ar_read_entry(b, e_name, &e_size, &e_mode)) {
return -1;
}
if (strncmp(h.end, AR_FILE_HEADER_END, sizeof(h.end))) {
RZ_LOG_ERROR("ar: Invalid header at offset 0x%" PFMT64x ": bad end field\n", h_off);
return -1;
}
// remove trailing spaces from fields and verify they are valid
VERIFY_AR_NUM_FIELD(h.timestamp, "timestamp")
VERIFY_AR_NUM_FIELD(h.oid, "oid")
VERIFY_AR_NUM_FIELD(h.gid, "gid")
VERIFY_AR_NUM_FIELD(h.mode, "mode")
VERIFY_AR_NUM_FIELD(h.size, "size")
if (h.size[0] == '\0') {
RZ_LOG_ERROR("ar: Malformed AR: bad size in header at offset 0x%" PFMT64x "\n", h_off);
return -1;
}
ut64 size = atol(h.size);
h.timestamp[0] = '\0'; // null terminate h.name
rz_str_trim_tail(h.name);
/*
* handle fake files
*/
if (!strcmp(h.name, "/")) {
if (!strcmp(e_name, "/")) {
// skip over symbol table
if (rz_buf_seek(b, size, RZ_BUF_CUR) <= 0 || rz_buf_tell(b) > arsize) {
if (rz_buf_seek(b, e_size, RZ_BUF_CUR) <= 0 || rz_buf_tell(b) > arsize) {
RZ_LOG_ERROR("ar: Malformed ar: too short\n");
return -1;
}
// return next entry
return ar_parse_header(arf, tbl, arsize);
} else if (!strcmp(h.name, "//")) {
return ar_parse_entry(arf, tbl, arsize);
} else if (!strcmp(e_name, "//")) {
// table of file names
if (tbl->data || tbl->size != 0) {
RZ_LOG_ERROR("ar: invalid ar file: two filename lookup tables (at 0x%" PFMT64x ", and 0x%" PFMT64x ")\n", tbl->offset, h_off);
RZ_LOG_ERROR("ar: invalid ar file: two filename lookup tables (at 0x%" PFMT64x ", and 0x%" PFMT64x ")\n", tbl->offset, e_offset);
return -1;
}
tbl->data = (char *)malloc(size + 1);
if (!tbl->data || rz_buf_read(b, (ut8 *)tbl->data, size) != size) {
tbl->data = (char *)malloc(e_size + 1);
if (!tbl->data || rz_buf_read(b, (ut8 *)tbl->data, e_size) != e_size) {
return -1;
}
tbl->data[size] = '\0';
tbl->size = size;
tbl->offset = h_off;
tbl->data[e_size] = '\0';
tbl->size = e_size;
tbl->offset = e_offset;
// return next entry
return ar_parse_header(arf, tbl, arsize);
return ar_parse_entry(arf, tbl, arsize);
}
/*
* handle real files
*/
RzList *list = rz_str_split_duplist(h.name, "/", false); // don't strip spaces
RzList *list = rz_str_split_duplist(e_name, "/", false); // don't strip spaces
if (rz_list_length(list) != 2) {
rz_list_free(list);
RZ_LOG_ERROR("ar: invalid ar file: invalid file name in header at: 0x%" PFMT64x "\n", h_off);
RZ_LOG_ERROR("ar: invalid ar file: invalid file name in header at: 0x%" PFMT64x "\n", e_offset);
return -1;
}
@ -177,7 +252,7 @@ static int ar_parse_header(RzArFp *arf, filetable *tbl, ut64 arsize) {
if (rz_str_isnumber(tmp)) {
arf->name = name_from_table(atol(tmp), tbl);
} else {
RZ_LOG_ERROR("ar: invalid ar file: invalid file name in header at: 0x%" PFMT64x "\n", h_off);
RZ_LOG_ERROR("ar: invalid ar file: invalid file name in header at: 0x%" PFMT64x "\n", e_offset);
}
free(tmp);
} else {
@ -185,7 +260,7 @@ static int ar_parse_header(RzArFp *arf, filetable *tbl, ut64 arsize) {
tmp = rz_list_pop(list);
if (tmp[0]) {
arf_clean_name(arf);
RZ_LOG_ERROR("ar: invalid ar file: invalid file name in header at: 0x%" PFMT64x "\n", h_off);
RZ_LOG_ERROR("ar: invalid ar file: invalid file name in header at: 0x%" PFMT64x "\n", e_offset);
}
free(tmp);
}
@ -194,19 +269,21 @@ static int ar_parse_header(RzArFp *arf, filetable *tbl, ut64 arsize) {
if (!arf->name) {
return -1;
}
arf->start = rz_buf_tell(b);
arf->end = arf->start + size;
arf->end = arf->start + e_size;
arf->st_mode = e_mode;
ar_sanitize_name(arf);
// skip over file content and make sure it is all there
if (rz_buf_seek(b, size, RZ_BUF_CUR) <= 0 || rz_buf_tell(b) > arsize) {
RZ_LOG_ERROR("ar: Malformed ar: missing the end of %s (header offset: 0x%" PFMT64x ")\n", arf->name, h_off);
if (rz_buf_seek(b, e_size, RZ_BUF_CUR) <= 0 || rz_buf_tell(b) > arsize) {
RZ_LOG_ERROR("ar: Malformed ar: missing the end of %s (header offset: 0x%" PFMT64x ")\n", arf->name, e_offset);
arf_clean_name(arf);
return -1;
}
return 1;
}
#undef VERIFY_AR_NUM_FIELD
/**
* \brief Open specific file withen a ar/lib file.
@ -216,6 +293,7 @@ static int ar_parse_header(RzArFp *arf, filetable *tbl, ut64 arsize) {
* Open an ar/lib and returns all the object files inside it.
*/
RZ_API RzList /*<RzArFp *>*/ *ar_open_all(const char *arname, int perm) {
ut32 fmode = 0;
if (!arname) {
rz_sys_perror(__FUNCTION__);
return NULL;
@ -256,12 +334,24 @@ RZ_API RzList /*<RzArFp *>*/ *ar_open_all(const char *arname, int perm) {
}
return NULL;
}
if ((res = ar_parse_header(arf, &tbl, arsize)) <= 0) {
if ((res = ar_parse_entry(arf, &tbl, arsize)) <= 0) {
// on error or when it has reached the EOF
free(tbl.data);
ar_close(arf);
return files;
}
// on linux the fmode is always 0, but arf->mode is non-zero
if (!arf->st_mode ||
((fmode = (arf->st_mode & S_IFMT)) && fmode != S_IFREG) ||
arf->start >= arf->end) {
// open only regular files.
// we do not need to close the buffer
arf->shared_buf = true;
ar_close(arf);
continue;
}
if (!rz_list_append(files, arf)) {
free(tbl.data);
ar_close(arf);
@ -310,7 +400,7 @@ RZ_API RzArFp *ar_open_file(const char *arname, int perm, const char *filename)
filetable tbl = { NULL, 0, 0 };
int r;
while ((r = ar_parse_header(arf, &tbl, arsize)) > 0) {
while ((r = ar_parse_entry(arf, &tbl, arsize)) > 0) {
if (filename) {
if (!strcmp(filename, arf->name)) {
// found the right file

View file

@ -1,7 +1,8 @@
// SPDX-FileCopyrightText: 2023 deroad <wargio@libero.it>
// SPDX-FileCopyrightText: 2017 xarkes <antide.petit@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#ifndef _AR_H
#define _AR_H
#ifndef RZ_AR_H
#define RZ_AR_H
#include <rz_util.h>
typedef struct RZARFP {
@ -10,6 +11,7 @@ typedef struct RZARFP {
ut64 end;
RzBuffer *buf;
bool shared_buf;
ut32 st_mode;
} RzArFp;
/* Offset passed is always the real io->off of the inspected file,
@ -19,4 +21,4 @@ RZ_API RzList /*<RzArFp *>*/ *ar_open_all(const char *arname, int perm);
RZ_API void ar_close(RzArFp *f);
RZ_API int ar_read_at(RzArFp *f, ut64 off, void *buf, int count);
RZ_API int ar_write_at(RzArFp *f, ut64 off, void *buf, int count);
#endif // _AR_H
#endif // RZ_AR_H

View file

@ -41,7 +41,7 @@ EOF
RUN
NAME=ar short with space
FILE=ar://bins/ar/filetable_spaces.a// s_spaces
FILE=ar://bins/ar/filetable_spaces.a//_s_spaces
CMDS=x 16
EXPECT=<<EOF
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
@ -50,7 +50,7 @@ EOF
RUN
NAME=ar long with space
FILE=ar://bins/ar/filetable_spaces.a// longgggggggggggggggggspaces
FILE=ar://bins/ar/filetable_spaces.a//_longgggggggggggggggggspaces_
CMDS=x 16
EXPECT=<<EOF
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
@ -276,3 +276,54 @@ nth paddr vaddr bind type size lib name
126 ---------- ---------- GLOBAL NOTYPE 0 imp.strtoull
EOF
RUN
NAME=Open all object in the win archive file.
FILE=ar://bins/ar/fpu.lib
CMDS=<<EOF
ol~ar://
EOF
EXPECT=<<EOF
3 * r-x 0x0000024f ar://bins/ar/fpu.lib//FpuXexpY.obj
4 - r-x 0x000001b3 ar://bins/ar/fpu.lib//FpuTrunc.obj
5 - r-x 0x000001e7 ar://bins/ar/fpu.lib//FpuTexpX.obj
6 - r-x 0x00000278 ar://bins/ar/fpu.lib//FpuTanh.obj
7 - r-x 0x000001cb ar://bins/ar/fpu.lib//FpuTan.obj
8 - r-x 0x00000239 ar://bins/ar/fpu.lib//FpuSub.obj
9 - r-x 0x000004a2 ar://bins/ar/fpu.lib//FpuState.obj
10 - r-x 0x000001d2 ar://bins/ar/fpu.lib//FpuSqrt.obj
11 - r-x 0x000001b2 ar://bins/ar/fpu.lib//FpuSize.obj
12 - r-x 0x000001ce ar://bins/ar/fpu.lib//FpuSinh.obj
13 - r-x 0x000001cb ar://bins/ar/fpu.lib//FpuSin.obj
14 - r-x 0x000001b3 ar://bins/ar/fpu.lib//FpuRound.obj
15 - r-x 0x00000239 ar://bins/ar/fpu.lib//FpuMul.obj
16 - r-x 0x000001d6 ar://bins/ar/fpu.lib//FpuLogx.obj
17 - r-x 0x000001d5 ar://bins/ar/fpu.lib//FpuLnx.obj
18 - r-x 0x000003a3 ar://bins/ar/fpu.lib//FpuFLtoA.obj
19 - r-x 0x00000195 ar://bins/ar/fpu.lib//FpuExam.obj
20 - r-x 0x000001e7 ar://bins/ar/fpu.lib//FpuEexpX.obj
21 - r-x 0x00000243 ar://bins/ar/fpu.lib//FpuDiv.obj
22 - r-x 0x000001cc ar://bins/ar/fpu.lib//FpuCosh.obj
23 - r-x 0x000001c9 ar://bins/ar/fpu.lib//FpuCos.obj
24 - r-x 0x0000022c ar://bins/ar/fpu.lib//FpuComp.obj
25 - r-x 0x0000018b ar://bins/ar/fpu.lib//FpuChs.obj
26 - r-x 0x0000031b ar://bins/ar/fpu.lib//FpuAtoFL.obj
27 - r-x 0x000001ab ar://bins/ar/fpu.lib//FpuArctanh.obj
28 - r-x 0x000001b8 ar://bins/ar/fpu.lib//FpuArctan.obj
29 - r-x 0x000001c1 ar://bins/ar/fpu.lib//FpuArcsinh.obj
30 - r-x 0x000001c0 ar://bins/ar/fpu.lib//FpuArcsin.obj
31 - r-x 0x000001b9 ar://bins/ar/fpu.lib//FpuArccosh.obj
32 - r-x 0x000001b0 ar://bins/ar/fpu.lib//FpuArccos.obj
33 - r-x 0x00000239 ar://bins/ar/fpu.lib//FpuAdd.obj
34 - r-x 0x0000018b ar://bins/ar/fpu.lib//FpuAbs.obj
EOF
RUN
NAME=Open only one object in the win archive file.
FILE=ar://bins/ar/ucrtd.osmode.lib//d_/os/obj/x86fre/minkernel/crts/ucrt/src/appcrt/dll/osmode_debug_public/../not_callable_by_os_objfre_i386_clog10l.obj
CMDS=<<EOF
ol
EOF
EXPECT=<<EOF
3 * r-x 0x000000b4 d_/os/obj/x86fre/minkernel/crts/ucrt/src/appcrt/dll/osmode_debug_public/../not_callable_by_os_objfre_i386_clog10l.obj
EOF
RUN