bin/137640: [PATCH] sh(1) crash when redefining current function
Jilles Tjoelker
jilles at stack.nl
Mon Aug 10 13:50:01 UTC 2009
>Number: 137640
>Category: bin
>Synopsis: [PATCH] sh(1) crash when redefining current function
>Confidential: no
>Severity: serious
>Priority: medium
>Responsible: freebsd-bugs
>State: open
>Quarter:
>Keywords:
>Date-Required:
>Class: sw-bug
>Submitter-Id: current-users
>Arrival-Date: Mon Aug 10 13:50:00 UTC 2009
>Closed-Date:
>Last-Modified:
>Originator: Jilles Tjoelker
>Release: FreeBSD 7.2-STABLE i386
>Organization:
None
>Environment:
System: FreeBSD snail.stack.nl 7.2-STABLE FreeBSD 7.2-STABLE #15: Thu Jun 11 11:26:27 CEST 2009 dean at meestal.stack.nl:/sabretooth.mnt/sources/7.x/i386/obj/sabretooth.mnt/sources/7.x/src/sys/STACK-SMP i386
also tested on 8.x
>Description:
sh(1) frees the memory of a function immediately when it is redefined or
unset, even if the function is currently being executed.
A use-after-free error.
>How-To-Repeat:
env MALLOC_OPTIONS=J sh -c 'g() { g() { :; }; :; }; g'
expected result: no output, return code 0
actual result: segmentation fault
Note that MALLOC_OPTIONS=J is default on -CURRENT.
>Fix:
Maintain a reference count on function definitions so they are kept until they
are no longer needed.
Idea is from dash.
--- func-redef-fix.patch begins here ---
Fix crash when undefining or redefining a currently executing function.
Memory may leak if multiple SIGINTs arrive in quick succession in
interactive mode, this will be fixed later by changing SIGINT handling.
diff --git a/eval.c b/eval.c
--- a/eval.c
+++ b/eval.c
@@ -791,6 +791,7 @@ evalcommand(union node *cmd, int flags,
INTOFF;
savelocalvars = localvars;
localvars = NULL;
+ cmdentry.u.func->refcount++;
INTON;
savehandler = handler;
if (setjmp(jmploc.loc)) {
@@ -800,6 +801,7 @@ evalcommand(union node *cmd, int flags,
freeparam(&shellparam);
shellparam = saveparam;
}
+ unreffunc(cmdentry.u.func);
poplocalvars();
localvars = savelocalvars;
handler = savehandler;
@@ -811,11 +813,12 @@ evalcommand(union node *cmd, int flags,
funcnest++;
exitstatus = oexitstatus;
if (flags & EV_TESTED)
- evaltree(cmdentry.u.func, EV_TESTED);
+ evaltree(&cmdentry.u.func->n, EV_TESTED);
else
- evaltree(cmdentry.u.func, 0);
+ evaltree(&cmdentry.u.func->n, 0);
funcnest--;
INTOFF;
+ unreffunc(cmdentry.u.func);
poplocalvars();
localvars = savelocalvars;
freeparam(&shellparam);
diff --git a/exec.c b/exec.c
--- a/exec.c
+++ b/exec.c
@@ -286,7 +286,7 @@ printentry(struct tblentry *cmdp, int ve
out1fmt("function %s", cmdp->cmdname);
if (verbose) {
INTOFF;
- name = commandtext(cmdp->param.func);
+ name = commandtext(&cmdp->param.func->n);
out1c(' ');
out1str(name);
ckfree(name);
@@ -583,7 +583,7 @@ deletefuncs(void)
while ((cmdp = *pp) != NULL) {
if (cmdp->cmdtype == CMDFUNCTION) {
*pp = cmdp->next;
- freefunc(cmdp->param.func);
+ unreffunc(cmdp->param.func);
ckfree(cmdp);
} else {
pp = &cmdp->next;
@@ -670,7 +670,7 @@ addcmdentry(char *name, struct cmdentry
INTOFF;
cmdp = cmdlookup(name, 1);
if (cmdp->cmdtype == CMDFUNCTION) {
- freefunc(cmdp->param.func);
+ unreffunc(cmdp->param.func);
}
cmdp->cmdtype = entry->cmdtype;
cmdp->param = entry->u;
@@ -705,7 +705,7 @@ unsetfunc(char *name)
struct tblentry *cmdp;
if ((cmdp = cmdlookup(name, 0)) != NULL && cmdp->cmdtype == CMDFUNCTION) {
- freefunc(cmdp->param.func);
+ unreffunc(cmdp->param.func);
delete_cmd_entry();
return (0);
}
diff --git a/exec.h b/exec.h
--- a/exec.h
+++ b/exec.h
@@ -50,7 +50,7 @@ struct cmdentry {
int cmdtype;
union param {
int index;
- union node *func;
+ struct funcdef *func;
} u;
int special;
};
diff --git a/mknodes.c b/mknodes.c
--- a/mknodes.c
+++ b/mknodes.c
@@ -248,8 +248,12 @@ output(char *file)
fputs("\tstruct nodelist *next;\n", hfile);
fputs("\tunion node *n;\n", hfile);
fputs("};\n\n\n", hfile);
- fputs("union node *copyfunc(union node *);\n", hfile);
- fputs("void freefunc(union node *);\n", hfile);
+ fputs("struct funcdef {\n", hfile);
+ fputs("\tunsigned int refcount;\n", hfile);
+ fputs("\tunion node n;\n", hfile);
+ fputs("};\n\n\n", hfile);
+ fputs("struct funcdef *copyfunc(union node *);\n", hfile);
+ fputs("void unreffunc(struct funcdef *);\n", hfile);
fputs(writer, cfile);
while (fgets(line, sizeof line, patfile) != NULL) {
diff --git a/nodes.c.pat b/nodes.c.pat
--- a/nodes.c.pat
+++ b/nodes.c.pat
@@ -35,6 +35,7 @@
#include <sys/param.h>
#include <stdlib.h>
+#include <stddef.h>
/*
* Routine for dealing with parsed shell commands.
*/
@@ -65,17 +66,22 @@ STATIC char *nodesavestr(char *);
* Make a copy of a parse tree.
*/
-union node *
+struct funcdef *
copyfunc(union node *n)
{
+ struct funcdef *fn;
+
if (n == NULL)
return NULL;
- funcblocksize = 0;
+ funcblocksize = offsetof(struct funcdef, n);
funcstringsize = 0;
calcsize(n);
- funcblock = ckmalloc(funcblocksize + funcstringsize);
+ fn = ckmalloc(funcblocksize + funcstringsize);
+ fn->refcount = 1;
+ funcblock = (char *)fn + offsetof(struct funcdef, n);
funcstring = (char *)funcblock + funcblocksize;
- return copynode(n);
+ copynode(n);
+ return fn;
}
@@ -146,12 +152,17 @@ nodesavestr(char *s)
/*
- * Free a parse tree.
+ * Decrement the reference count of a function definition, freeing it
+ * if it falls to 0.
*/
void
-freefunc(union node *n)
+unreffunc(struct funcdef *fn)
{
- if (n)
- ckfree(n);
+ if (fn) {
+ fn->refcount--;
+ if (fn->refcount > 0)
+ return;
+ ckfree(fn);
+ }
}
--- func-redef-fix.patch ends here ---
>Release-Note:
>Audit-Trail:
>Unformatted:
More information about the freebsd-bugs
mailing list