-
Notifications
You must be signed in to change notification settings - Fork 159
VScript member function call safety #436
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
VScript member function call safety #436
Conversation
|
A simpler test code would be: access uninitialised stack variable interpret crash via invalid offset The results of access uninitialised stack variable crash via null ptr deref crash via invalid offset
|
|
I'm afk for the rest of the week. I'd amend the constructor(_stub) when I'm back, or you can merge the current branch and do it yourself as well if you don't want to wait.
I wasn't aware of the .call() syntax. I think I've looked at the construction functions, but thought it would't be possible to misuse.
|
3e2f0f9 to
7b96eb0
Compare
|
I tested that all of the provided scripts throw an error (and none cause a crash). |
7b96eb0 to
bc412a3
Compare
… has userpointer type This prevents memory errors when incorrectly invoking native member functions from Squirrel.
bc412a3 to
f6b27ab
Compare
|
The latest changes also fix class C{}
Vector.constructor.call( C() ) |
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.
"Expected class userpointer" error doesn't really make sense, the data is attached to the instance. sq_getinstanceup already sets the error message, I feel like we could just let that pass and return SQ_ERROR. "invalid type tag" error is as vague as "Expected userpointer" to the end user anyway.
The way to access get/set/tostring_stub through the debugger is to view class metamethods, copy the function address, then call or assign it from variable watches (*0x00000000).call(null). Exploiting this requires a malicious client to connect locally or through an open port to the debugger which the user had to have enabled themselves. It doesn't really hurt to check for them too though.
Somewhat related issues I just noticed that you could fix here if you want to:
sq_resetobject(&pSquirrelVM->lastError_) lines in constructor_stub and function_stub can be removed since that object can only change through SquirrelVM::RaiseException which should only be called from function_stub binding call m_pfnBinding() where lastError_ is reset if it was changed.
The error message is also leaked; sq_release should be called on lastError_ after pushing it to the stack on if (!sq_isnull(pSquirrelVM->lastError_)) check in function_stub.
In getVariant() OT_INSTANCE case, sq_gettypetag check can be removed because it is only used for TYPETAG_VECTOR validation which is already done by sq_getinstanceup.
An irrelevant issue, SQVector::{_get, _set} are accessing out of bounds at index 48 with key['0']. I remember seeing this years ago but I forgot about it.
sp/src/vscript/vscript_squirrel.cpp
Outdated
|
|
||
| SQUserPointer userptr = nullptr; | ||
| sq_getuserpointer(vm, top, &userptr); | ||
| if (SQ_FAILED(sq_getuserpointer(vm, top, &userptr))) |
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 check isn't needed, it is guaranteed to be a userpointer from SQVM::CallNative due to closure registration sq_newclosure(vm_, function_stub, 1);
This line also could be simplified by removing userptr variable: sq_getuserpointer(vm, top, (SQUserPointer*)&pFunc);
sp/src/vscript/vscript_squirrel.cpp
Outdated
| // expect construction to always succeed | ||
| Assert(sq_isnull(pSquirrelVM->lastError_)); | ||
| // expect construction to always succeed | ||
| Assert(sq_isnull(pSquirrelVM->lastError_)); |
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.
sq_getsharedforeignptr line should be after m_pfnConstruct() line because it is only used for lastError_ assertion. This extra block indentation could also be removed, it's a bit out of place in this small function.
sp/src/vscript/vscript_squirrel.cpp
Outdated
| { | ||
| SQUserPointer self; | ||
| sq_getinstanceup(vm, 1, &self, nullptr); | ||
| if (SQ_FAILED(sq_getinstanceup(vm, 1, &self, 0))) |
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 could also be ClassInstanceData *self; and sq_getinstanceup(vm, 1, (SQUserPointer*)&self, 0)
Having known types makes it easier to inspect in debuggers.
…on call This enhances the ScriptFuncDescriptor_t to record the ScriptClassDesc_t of the class for which it gets invoked. The ScriptClassDesc_t are traversed in the function_stub() for native function calls for member functions, to ensure the passed in instance has a compatible type. This prevents memory errors when incorrectly invoking native member functions from Squirrel.
This prevents manual invocations of the Vector.constructor with an invalid value.
f6b27ab to
b087346
Compare
|
Thanks for the review @samisalreadytaken. I checked off all your suggestions (the 6 points in your overview comment and the 3 inline comments). |
| SquirrelVM* pSquirrelVM = (SquirrelVM*)sq_getsharedforeignptr(vm); | ||
| Assert(pSquirrelVM); | ||
| // expect construction to always succeed | ||
| Assert(sq_isnull(pSquirrelVM->lastError_)); |
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.
Does this still need a sq_resetobject(&pSquirrelVM->lastError_); before pClassDesc->m_pfnConstruct()? I don't seem to have issues without it though. I guess sq_release() also nulls the object?
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.
Not in here, we previously established that constructors don't raise script exceptions; it needs to be reset after returning it to the script on function_stub (I guess I wasn't clear on that). sq_release doesn't nullify the input, RaiseException resetting lastError_ is irrelevant
lastError_ is a temporary object that is only meant to carry its state from within m_pfnBinding() call to function_stub() body.
if lastError_ is not null
sq_pushobject lastError_
sq_release lastError_
sq_resetobject lastError_
This is the same logic of returning the result of RegisterInstance and HSCRIPT_RC if you recall that.
89202e0 to
5355a52
Compare
samisalreadytaken
left a comment
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.
…meters This prevents manual invocations of the native class constructor for non-class values or non-native classes.
This prevents manual invocations of the native class constructor with an invalid value.
The function_stub() resets it when used.
Also streamline SQUserPointer usage in sq_getinstanceup() and sq_getuserpointer() calls to write directly to a pointer of the expected type.
This fixes a memory leak.
5355a52 to
c659af5
Compare
This inserts some checks on the script instance for which to invoke a native member function on.
The first commit prevents this from crashing(/corrupting) the game:
and the second commit, this
PR Checklist
developbranch OR targets another branch with a specific goal in mind