-
Notifications
You must be signed in to change notification settings - Fork 94
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Modify ReturnStatement to prevent a UaF #76
base: master
Are you sure you want to change the base?
Conversation
Signed-off-by: pitust <[email protected]>
Signed-off-by: pitust <[email protected]>
Signed-off-by: pitust <[email protected]>
@@ -113,45 +113,43 @@ export class CVariableAllocation extends CTemplateBase { | |||
} | |||
|
|||
@CodeTemplate(` | |||
{#statements} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think {#statements}
was there for a reason...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really know, but i know the tests pass... I removed statements because it moved it above the actual return code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tested it and basically the main side effect is that it inserts empty lines in random places if destructors are not within {#statements}
block. so even though it's not critical, but I would say that in the best case scenario we probably want to keep {#statements}
.
I think if {returnTypeAndVar} = {expression};
line is also put into {#statements}
then it will appear above destructors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think if {returnTypeAndVar} = {expression}; line is also put into {#statements} then it will appear above destructors.
my assumption was incorrect
I guess we will have to get rid of {#statements}
after all, but it would still be great to fix the whitespaces somehow. I'm looking into it now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
General whitespace handling improvements were done in c27ad6d
However, I noticed there's another side effect of removing {#statements}
from destructors: it adds destructors after return
in some cases. It is an unreachable code, that's why tests weren't failing, but it's quite bad still, obviously.
I'll have to investigate and fix it.
Sorry, but I cannot accept this PR in it's current form. Also, would be better to at least split out debugging stuff into a separate PR (or drop it altogether). You can of course keep it in your fork if it helps you. But I am not sure I can merge it before I fully understand why we need this extra condition in templating engine. |
@andrei-markeev i think that's it. It also fixes an internal bug in the templater (the prototype wasn't reassigned in the decorator). |
hey! thanks for the update and sorry for the delay with response. NY time, quite hectic 😓 I found some more cases where this problem exists. Will investigate, and get back to you! |
@andrei-markeev Are there any pending issues in this PR? If not, please merge this. |
let s = scope.root.memoryManager.getDestructorsForScope(node); | ||
let result = false; | ||
for (let e of s) { | ||
result = result || new RegExp('\\W' + e.varName + '\\W').test(' ' + node.getFullText() + ' '); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am afraid relying on a text match here is too fragile and error-prone.
return statement can be huge, for example something like return function() { /* ... 200 lines of code here ... */ }
, or something like return translate("Lorem ipsum dolor ... 5000 characters of random text here")
... it can contain any imaginable text and it will not mean anything.
the right way would be to do something similar to what we do in memory.ts i.e. properly analyse the return statement and if a subkey of an object that was scheduled to be disposed in this scope escapes into the upper function, then we need to change the return statement to use the temporary variable.
// todo: make this less hackyy | ||
let fakeVar = new CVariable(scope, '__fake', returnType, { removeStorageSpecifier: true, arraysToPointers: true });; | ||
let fakeVarType = fakeVar.resolve().slice(0, -6).trim(); | ||
this.returnTypeAndVar = fakeVarType; | ||
if (this.returnTypeAndVar.indexOf('{var}') == -1) { | ||
this.returnTypeAndVar += ' {var}'; | ||
} | ||
this.returnTypeAndVar = this.returnTypeAndVar.replace('{var}', this.returnTemp); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is not only too hacky, it is also not a proper C89 as it produces a variable declaration that is not in the beginning of the block:
mem/mem7.c: In function ‘return_from_obj’:
mem/mem7.c:23:5: warning: ISO C90 forbids mixed declarations and code [-Wdeclaration-after-statement]
23 | struct return_from_obj_t * returnVal = obj->key;
| ^~~~~~
it's very easy to fix though, you just need to do smth like this:
if (tempVarIsNeeded) {
this.retVarName = scope.root.symbolsHelper.addTemp(node, 'returnVal');
let returnType = scope.root.typeHelper.getCType(node.expression);
scope.variables.push(new CVariable(scope, this.retVarName, returnType, { removeStorageSpecifier: true }))
}
pushing the variable into the scope variables will automatically add it's declaration into the right place
this.returnTemp = scope.root.symbolsHelper.addTemp(node, 'returnVal'); | ||
let returnType = scope.root.typeHelper.getCType(node.expression); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's a good idea to move those two lines also into the if block because otherwise we're reserving the temp variable name even when it is not really needed
@@ -113,45 +113,43 @@ export class CVariableAllocation extends CTemplateBase { | |||
} | |||
|
|||
@CodeTemplate(` | |||
{#statements} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tested it and basically the main side effect is that it inserts empty lines in random places if destructors are not within {#statements}
block. so even though it's not critical, but I would say that in the best case scenario we probably want to keep {#statements}
.
I think if {returnTypeAndVar} = {expression};
line is also put into {#statements}
then it will appear above destructors.
I experimented a bit more, and if we expand the test case for key to be non-primitive, then it doesn't work anymore: function return_from_obj() {
let obj = { key: { hello: "world" } };
return obj.key;
}
console.log(return_from_obj()); this produces the following generated code: struct return_from_obj_t {
const char * hello;
};
struct obj_t {
struct return_from_obj_t * key;
};
static struct return_from_obj_t * tmp_result;
struct return_from_obj_t * return_from_obj()
{
struct obj_t * obj;
struct return_from_obj_t * tmp_obj = NULL;
struct return_from_obj_t * ret;
tmp_obj = malloc(sizeof(*tmp_obj));
assert(tmp_obj != NULL);
tmp_obj->hello = "world";
obj = malloc(sizeof(*obj));
assert(obj != NULL);
obj->key = tmp_obj;
ret = obj->key;
free(obj);
free(tmp_obj); // <--- wrong, essentially frees `obj->key`
return ret;
} So the bigger issue here is that escape mechanism doesn't correctly detect escaping of subkeys via return statements. I'll try to add it. |
Escape analysis is fixed in 0bda2b3 |
This prevents a use after free and a valgrind error like this:
This also adds a nodejs-only environment variable DEBUG which, when set, produces a wealth of debug output (aka backtraces on every template instantiation).
Fixes #74