librz/type: fix orphan '*' when emitting pf format for typedef'd void*/char* (#6420)
When type_to_format / type_to_format_pair encounter a pointer field whose
pointee is reachable only through a typedef chain that ends at the
atomic 'void' or 'char' (e.g. PVOID -> VOID -> void, LPSTR -> CHAR ->
char, HANDLE -> ... -> void in some platform headers), they emit a bare
'*' and recurse into the pointee. The recursion produces nothing for
'void' (no format) and emits the legacy 'c' for 'char', so the
generated pf string ends up with either an orphan trailing/internal '*'
or the sequence '*c', which the new pf parser introduced in the
recent rewrite rejects: it requires '*' to be followed by a complete
dereferenceable spec ('z', a sized integer like 'd4'/'x2'/'u8', or
'?'). The result is parser warnings and dropped fields during 'tp':
rizin -k windows -c 'tp _OBJECT_ATTRIBUTES'
WARNING: pf: unknown specifier '*' at position 4, skipping
WARNING: pf: unknown specifier '*' at position 4, skipping
[only 4 of 6 fields rendered]
rizin -k windows -c 'tp _SYSTEM_INFO'
[11 fields collapse to 4]
rizin -k windows -c 'tp _STARTUPINFOA'
[three LPSTR fields render as '*c' which the parser cannot
usefully follow]
The top-level rz_type_as_format() already special-cases 'void *',
'char *', and callable pointers, mapping them to 'p' and 'z'. But the
inner walkers do not, because rz_type_is_void_ptr / rz_type_is_char_ptr
compare the literal identifier name and so do not see through typedefs
like VOID->void or CHAR->char.
Fix it by adding ptr_pointee_resolves_to(), a small static helper in
format.c that walks the typedb to find the canonical atomic name for
the pointee (bounded depth so a circular typedef cannot send the
resolver into an infinite loop), and using it in both POINTER branches
before the '*'+recurse fallback. Pointers whose pointee resolves to
'void' (or a void-aliased typedef) now emit 'p'; pointers whose pointee
resolves to 'char' (or a char-aliased typedef) emit 'z'. Everything
else continues to emit '*<inner>' unchanged, so single-level
LPBYTE -> BYTE -> unsigned char still renders as the perfectly valid
'*x1', and pointer-to-struct '*?' chains are untouched.
After the fix, the same upstream structs produce well-formed pf
strings:
ts _OBJECT_ATTRIBUTES -> 'x8p**x1x8pp ...' (two PVOID -> pp)
ts _SYSTEM_INFO -> 'x2x2d4ppx8d4d4d4x2x2 ...' (two LPVOID -> pp)
ts _STARTUPINFOA -> 'd4zzzd4...*x1ppp ...' (three LPSTR -> zzz;
LPBYTE still '*x1')
Add a regression test in test/db/cmd/cmd_pf that exercises both shapes
via 'ts _SECURITY_ATTRIBUTES' (single LPVOID) and 'ts _STARTUPINFOA'
(three LPSTR + one LPBYTE). Without this commit the test catches the
bug -- the LPVOID field becomes orphan '*' inside the format and the
LPSTR fields render as '*c'; with the commit both render cleanly as
documented above.
Also update one existing EXPECT in test/db/cmd/types: the 'td with
comments' test had encoded the legacy 'pf "d4[5]*c b foo"' shape for
'char *foo[5]'. With the fix this becomes 'pf "d4[5]z b foo"', which
is both well-formed under the new DSL and a more accurate description
(an array of zstrings rather than an array of pointers to a single
char).
Co-authored-by: Anton Kochkov <anton.kockov@gmail.com>
This commit is contained in:
parent
e6e3ca6abf
commit
1a0c5c655a
4 changed files with 88 additions and 7 deletions
|
|
@ -256,6 +256,49 @@ RZ_API RZ_OWN char *rz_type_format(RZ_NONNULL const RzTypeDB *typedb, RZ_NONNULL
|
|||
return rz_base_type_as_format(typedb, btype);
|
||||
}
|
||||
|
||||
/* True iff `type` is a POINTER whose pointee resolves -- by walking
|
||||
* through typedef chains in the typedb -- to an atomic base type named
|
||||
* exactly `atomic_name`. This is the typedb-aware counterpart of the
|
||||
* rz_type_is_*_ptr helpers in librz/type/helpers.c, which compare the
|
||||
* raw identifier name and so do not see through typedef chains like
|
||||
* PVOID -> VOID -> void or LPSTR -> CHAR -> char. Walks at most
|
||||
* RZ_TYPE_FORMAT_PTR_RESOLVE_MAX_DEPTH typedef hops so a circular
|
||||
* typedef cannot send the resolver into an infinite loop.
|
||||
*/
|
||||
#define RZ_TYPE_FORMAT_PTR_RESOLVE_MAX_DEPTH 16
|
||||
|
||||
static bool ptr_pointee_resolves_to(const RzTypeDB *typedb, const RzType *type, const char *atomic_name) {
|
||||
if (!type || type->kind != RZ_TYPE_KIND_POINTER || !atomic_name) {
|
||||
return false;
|
||||
}
|
||||
const RzType *ptr = type->pointer.type;
|
||||
if (!ptr || ptr->kind != RZ_TYPE_KIND_IDENTIFIER ||
|
||||
ptr->identifier.kind != RZ_TYPE_IDENTIFIER_KIND_UNSPECIFIED ||
|
||||
!ptr->identifier.name) {
|
||||
return false;
|
||||
}
|
||||
const char *cur_name = ptr->identifier.name;
|
||||
for (int i = 0; i < RZ_TYPE_FORMAT_PTR_RESOLVE_MAX_DEPTH && cur_name; i++) {
|
||||
if (!strcmp(cur_name, atomic_name)) {
|
||||
return true;
|
||||
}
|
||||
RzBaseType *btyp = rz_type_db_get_base_type(typedb, cur_name);
|
||||
if (!btyp) {
|
||||
return false;
|
||||
}
|
||||
if (btyp->kind == RZ_BASE_TYPE_KIND_ATOMIC) {
|
||||
return btyp->name && !strcmp(btyp->name, atomic_name);
|
||||
}
|
||||
if (btyp->kind != RZ_BASE_TYPE_KIND_TYPEDEF || !btyp->type ||
|
||||
btyp->type->kind != RZ_TYPE_KIND_IDENTIFIER ||
|
||||
!btyp->type->identifier.name) {
|
||||
return false;
|
||||
}
|
||||
cur_name = btyp->type->identifier.name;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void type_to_format(const RzTypeDB *typedb, RzStrBuf *buf, RzType *type) {
|
||||
if (type->kind == RZ_TYPE_KIND_IDENTIFIER) {
|
||||
const char *format = rz_type_db_format_get(typedb, type->identifier.name);
|
||||
|
|
@ -280,6 +323,26 @@ static void type_to_format(const RzTypeDB *typedb, RzStrBuf *buf, RzType *type)
|
|||
rz_strbuf_appendf(buf, "[%" PFMT64d "]", type->array.count);
|
||||
type_to_format(typedb, buf, type->array.type);
|
||||
} else if (type->kind == RZ_TYPE_KIND_POINTER) {
|
||||
// Pointer-to-void via a typedef chain (PVOID -> VOID -> void,
|
||||
// LPVOID -> PVOID -> VOID -> void, HANDLE -> ... -> void) must
|
||||
// emit a self-contained `p` token rather than the recursive
|
||||
// `*<inner>` fallback, which would leave an orphan `*` because
|
||||
// `void` has no pf format of its own. The rz_type_is_void_ptr
|
||||
// helper only matches the raw identifier name "void", so it
|
||||
// does not see through these typedef chains;
|
||||
// ptr_pointee_resolves_to does.
|
||||
//
|
||||
// Pointer-to-char is intentionally NOT folded here: the
|
||||
// recursive walker already produces `*c` (pointer-deref to a
|
||||
// 1-byte signed char), which is a valid pf spec under the new
|
||||
// parser and is what callers such as `avgp` for a `char *`
|
||||
// global variable already expect (showing the pointer literal
|
||||
// rather than reinterpreting the pointer bytes as an inline
|
||||
// string).
|
||||
if (ptr_pointee_resolves_to(typedb, type, "void")) {
|
||||
rz_strbuf_append(buf, "p");
|
||||
return;
|
||||
}
|
||||
rz_strbuf_append(buf, "*");
|
||||
type_to_format(typedb, buf, type->pointer.type);
|
||||
}
|
||||
|
|
@ -339,6 +402,17 @@ static bool type_to_format_pair(const RzTypeDB *typedb, RzStrBuf *format, RzStrB
|
|||
if (name) {
|
||||
rz_strbuf_appendf(fields, "%s ", name);
|
||||
}
|
||||
} else if (ptr_pointee_resolves_to(typedb, type, "void")) {
|
||||
// Same orphan-`*` issue as in type_to_format: emit a
|
||||
// self-contained `p` and the field name so the resulting
|
||||
// pair (e.g. "p" + "(PVOID)lpSecurityDescriptor")
|
||||
// parses cleanly under the new pf DSL. Pointer-to-char
|
||||
// is intentionally NOT folded -- see the matching comment
|
||||
// in type_to_format.
|
||||
rz_strbuf_append(format, "p");
|
||||
if (identifier) {
|
||||
rz_strbuf_appendf(fields, "%s ", identifier);
|
||||
}
|
||||
} else {
|
||||
rz_strbuf_append(format, "*");
|
||||
return type_to_format_pair(typedb, format, fields, identifier, type->pointer.type);
|
||||
|
|
|
|||
|
|
@ -315,7 +315,7 @@ RZ_API bool rz_type_is_void_ptr(RZ_NONNULL const RzType *type) {
|
|||
}
|
||||
|
||||
/**
|
||||
* \brief Checks if the pointer RzType is a nested abstract pointer ("void **", "vpod ***", etc)
|
||||
* \brief Checks if the pointer RzType is a nested abstract pointer ("void **", "void ***", etc)
|
||||
*
|
||||
* \param type RzType type pointer
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -316,12 +316,6 @@ void pf_render_guid(RzStrBuf *sb, const ut8 *b, RzPfGuidLayout lay) {
|
|||
b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]);
|
||||
}
|
||||
|
||||
// Render: text
|
||||
|
||||
// Render: C struct
|
||||
|
||||
// Render: dot graph (Graphviz `digraph` record nodes)
|
||||
|
||||
// Unified render dispatcher
|
||||
/**
|
||||
* \brief Render an array of values to a string in the given mode.
|
||||
|
|
|
|||
|
|
@ -254,6 +254,19 @@ EXPECT=<<EOF
|
|||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pf format from typedef'd void* / char* (orphan-* regression)
|
||||
FILE==
|
||||
ARGS=-a x86 -b 64 -k windows
|
||||
CMDS=<<EOF
|
||||
ts _SECURITY_ATTRIBUTES
|
||||
ts _STARTUPINFOA
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
pf "d4pd4 nLength (LPVOID)lpSecurityDescriptor bInheritHandle"
|
||||
pf "d4*c*c*cd4d4d4d4d4d4d4d4x2x2*x1ppp cb (LPSTR)lpReserved (LPSTR)lpDesktop (LPSTR)lpTitle dwX dwY dwXSize dwYSize dwXCountChars dwYCountChars dwFillAttribute dwFlags wShowWindow cbReserved2 (LPBYTE)lpReserved2 hStdInput hStdOutput hStdError"
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=timestamp
|
||||
FILE=malloc://1024
|
||||
CMDS=<<EOF
|
||||
|
|
|
|||
Loading…
Reference in a new issue