Lock noreturn inputs only for known empty prototypes

This commit is contained in:
phix33 authored and pancake committed 2026-09-16 14:20:18 +08:00
1 parent 9e36cfd88e
commit a59c4ecbc1
6 files changed
+90 -35

No files matched your search

+14 -17
View File
@@ -535,42 +535,39 @@ static void processFunctionSignature(
}
#endif
static bool protoTakesArgs(RCore *core, const char *name) {
static bool protoHasNoArgs(RCore *core, const char *name) {
Sdb *tdb = core->anal->sdb_types;
char *fname = r_type_func_guess (tdb, r_str_skip_prefix (name, "reloc."));
if (!fname) {
return false;
}
// args_count is 0 for both unknown and zero-arg protos, so > 0 alone tells real arg-takers apart
bool has_args = r_type_func_args_count (tdb, fname) > 0;
free (fname);
return has_args;
char *key = r_type_func_key (tdb, name);
// a missing args key is an unknown prototype, not an empty one
const char *args = key? sdb_const_getf (tdb, nullptr, "func.%s.args", key): nullptr;
free (key);
return args && !strcmp (args, "0");
}
// Ghidra does active param recovery unless input is locked, so a no-arg noreturn (eg __stack_chk_fail) would otherwise get a live caller reg as a phantom arg
static void markNoReturn(FunctionSymbol *funcsym, bool takesArgs) {
static void markNoReturn(FunctionSymbol *funcsym, bool lockNoArgs) {
Funcdata *fd = funcsym? funcsym->getFunction (): nullptr;
if (!fd) {
return;
}
FuncProto &fp = fd->getFuncProto ();
fp.setNoReturn (true);
if (!takesArgs) {
if (lockNoArgs) {
fp.clearInput ();
fp.setInputLock (true);
}
}
static bool noreturnTakesArgs(RCore *core, RAnalFunction *fcn, const char *fcn_name) {
static bool noreturnHasNoArgs(RCore *core, RAnalFunction *fcn, const char *fcn_name) {
RAnalVar **it;
R_VEC_FOREACH (&fcn->vars, it) {
RAnalVar *v = *it;
// a ppc PLT stub's TOC save lands a spurious stack arg, so only a register arg counts
if (v && v->isarg && v->kind == R_ANAL_VAR_KIND_REG) {
return true;
return false;
}
}
return protoTakesArgs (core, fcn_name);
return protoHasNoArgs (core, fcn_name);
}
FunctionSymbol *R2Scope::registerFunction(RAnalFunction *fcn) const {
@@ -729,8 +726,8 @@ FunctionSymbol *R2Scope::registerFunction(RAnalFunction *fcn) const {
fd->getFuncProto().setPieces(sig_proto);
}
}
if (fcn->is_noreturn && !noreturnTakesArgs (core, fcn, fcn_name)) {
markNoReturn (funcsym, false);
if (fcn->is_noreturn) {
markNoReturn (funcsym, noreturnHasNoArgs (core, fcn, fcn_name));
}
return funcsym;
}
@@ -741,7 +738,7 @@ FunctionSymbol *R2Scope::registerFunctionFlag(RFlagItem *flag) const {
? flag->realname : flag->name;
FunctionSymbol *funcsym = cache->addFunction (Address (arch->getDefaultCodeSpace (), flag->addr), name);
if (r_anal_noreturn_at (core->anal, flag->addr)) {
markNoReturn (funcsym, protoTakesArgs (core, name));
markNoReturn (funcsym, protoHasNoArgs (core, name));
}
return funcsym;
}
+18
View File
@@ -55,3 +55,21 @@ elf/x86_64-varargs.so: varargs.c
x86_64-linux-gnu-gcc -shared -fPIC -O1 -U_FORTIFY_SOURCE \
-Wl,-z,lazy,-z,norelro,-z,max-page-size=0x1000,--build-id=none \
-o $@ varargs.c
elf/arm64-noreturn-etrel.o: arm64-noreturn-etrel.c
aarch64-linux-gnu-gcc -O2 -c \
-fstack-protector-all \
-fno-asynchronous-unwind-tables \
-fno-unwind-tables \
-fno-ident \
-fcf-protection=none \
-o $@ arm64-noreturn-etrel.c
aarch64-linux-gnu-strip --strip-debug $@
elf/noreturn-prototypes-x86_64.o: noreturn-prototypes.c
x86_64-linux-gnu-gcc -O2 -c \
-fno-stack-protector \
-fno-asynchronous-unwind-tables \
-fno-unwind-tables \
-fno-ident \
-o $@ noreturn-prototypes.c
+17
View File
@@ -0,0 +1,17 @@
#include <stdlib.h>
#include <string.h>
int sum(const char *s) {
char buf[32];
strncpy (buf, s, sizeof (buf) - 1);
buf[sizeof (buf) - 1] = 0;
int r = 0;
for (char *p = buf; *p; p++) {
r += *p;
}
return r;
}
void quit(void) {
exit (42);
}
Binary file not shown.
+15
View File
@@ -0,0 +1,15 @@
#include <err.h>
extern void custom_fatal(int status) __attribute__((noreturn));
void call_err(void) {
err (42, "boom");
}
void call_errx(void) {
errx (42, "boom");
}
void call_custom(void) {
custom_fatal (42);
}
+26 -18
View File
@@ -1591,40 +1591,48 @@ EXPECT=<<EOF2
EOF2
RUN
NAME=aarch64 ET_REL decompile stops at a noreturn reloc call
NAME=aarch64 ET_REL noreturn stops flow and preserves argument counts
FILE=bins/elf/arm64-noreturn-etrel.o
ARGS=-e bin.relocs.apply=true
CMDS=<<EOF2
aaa
pdg @ sym.sum~Subroutine does not return,strncpy,exit([0]
EOF2
EXPECT=<<EOF2
puVar2
//WARNING:
EOF2
RUN
NAME=aarch64 ET_REL noreturn reloc callee gets no phantom canary args
FILE=bins/elf/arm64-noreturn-etrel.o
ARGS=-e bin.relocs.apply=true
CMDS=<<EOF2
aaa
pdg @ sym.sum~stack_chk_fail()?
pdg @ sym.sum~?int sym.sum(
pdg @ sym.sum~?stack_chk_fail()
pdg @ sym.sum~?reloc.exit,halt_baddata
pdg @ sym.quit~?exit(0x2a
EOF2
EXPECT=<<EOF2
1
1
0
1
EOF2
RUN
NAME=aarch64 ET_REL noreturn reloc callee that takes an argument keeps it
FILE=bins/elf/arm64-noreturn-etrel.o
NAME=noreturn flags preserve known and unknown arguments
FILE=bins/elf/noreturn-prototypes-x86_64.o
ARGS=-e bin.relocs.apply=true
CMDS=<<EOF2
aaa
pdg @ sym.quit~?exit(0x2a
pdg @ sym.call_err~?reloc.err(0x2a
pdg @ sym.call_errx~?reloc.errx(0x2a
tna reloc.custom_fatal
pdg @ sym.call_custom~?reloc.custom_fatal(0x2a)
pdg @ sym.call_custom~?halt_baddata
tk custom_fatal=func
pdg @ sym.call_custom~?reloc.custom_fatal(0x2a)
tk func.custom_fatal.args=0
tk reloc.custom_fatal=func
tk func.reloc.custom_fatal.args=1
pdg @ sym.call_custom~?reloc.custom_fatal(0x2a)
EOF2
EXPECT=<<EOF2
1
1
1
0
1
1
EOF2
RUN