Advertisement

Recover from assertion in LoadByteCode

Started by October 11, 2021 11:47 AM
4 comments, last by WitchLord 3Β years, 1Β month ago

Hi,

I am trying to extend the functionality of a remote embedded system with AngelCode scripts. Because my communication with the target may be faulty, I cannot assure that the bytecode I am trying to load actually makes sense. Thus, I ran some tests to fuzz the target with random code and that triggered some assertions in the library. This leads to a failure of the system and thereby allows for denial-of-service attacks.

Can I somehow handle these errors more gracefully (e.g. with a code-validation routine before loading) so I can side-step the assertions?

Cheers and big thanks

PS: I asked this in a different thread before, but I can't find my comment anymore. Hope this is not a double post or violating TOS.

Hi EngineNo.9,

The assert failures in the library are not meant to be side-stepped. But to your relief, an assert failure inside the library is not supposed to happen on invalid bytecode. The library should do everything possible to generate a graceful error upon invalid bytecode. With your tests you have obviously identified some path that is not having proper validation in the library and hence it failed with an assert.

Can you provide a sample code that reproduces the problem so I can debug it and fix the library?

EngineNo.9 said:
Can I somehow handle these errors more gracefully (e.g. with a code-validation routine before loading) so I can side-step the assertions?

While the library is supposed to verify that the loaded bytecode is valid, it cannot assure that the code has not been tampered with by some hacker. You will need to add proper validation on the bytecode before passing it on to the library to guarantee that the code is original and coming from a trusted source. This is an entirely different topic, and probably involves using digital signatures with public-private keys. I haven't done a lot of work around this so I can't help you much on this, but I'm sure that with a little bit of research you'll be able to figure it out.

Regards,
Andreas

AngelCode.comΒ - game development and more - Reference DBΒ - game developer references
AngelScriptΒ - free scripting library -Β BMFontΒ - free bitmap font generator - TowerΒ - free puzzle game

Advertisement

If you're trying to protect against faulty transmission, use checksums. If you're trying to protect against impersonation attacks, use public key signatures. But don't ever try loading byte code if you haven't already verified that the code is safe to load. That's just about the worst security vulnerability that your code can have, since byte code is executable code.

Hi,

a light breeze said:
But don't ever try loading byte code if you haven't already verified that the code is safe to load.

I am aware of the situation and will use checksums and signed code. That still assumes that the original code made sense though (i.e. that I haven't made a mistake when compiling/storing/uploading it). I guess I could try to load the code into a local VM before sending it, to validate that it is reasonable code. Still there is something like a TOCTOU situation where the code I validated locally is not the code I upload. But I guess you're right - if I take enough precautions, the chances of this happening are very slim.

WitchLord said:
Can you provide a sample code that reproduces the problem so I can debug it and fix the library?

Okay so I used the asrun sample and changed it to load bytecode instead:

	// Load the script code
    FILE* fp = fopen(argv[scriptArg], "r");
    if (fp == nullptr)
    {
        cout << "Error opening bytecode file." << endl;

		WaitForUser();
		return -1;
    }

    ByteCodeFileReader codeReader(fp);
    asIScriptModule* mod = engine->GetModule("script", asGM_ALWAYS_CREATE);
    bool wasDebugInfoStripped;
    mod->LoadByteCode(&codeReader, &wasDebugInfoStripped);
	if (mod == nullptr)
	{
        cout << "Error loading bytecode file." << endl;

		WaitForUser();
		return -1;
	}

	// Execute the script
	r = ExecuteScript(engine, argv[scriptArg]);

The bytecode buffer simply reads from a file:

public:

    explicit ByteCodeFileReader(FILE* fp) : mFile(fp)
    {
    }

    int
    Read(void* ptr, asUINT size) override
    {
        if (size == 0)
            return 0;
        size_t read = fread(ptr, 1, size, mFile);
        if (read != size)
            return -1;
        return 0;
    }

private:
    FILE* mFile;

Now if I compile the script.as in asbuild and run that, I get the β€œhello world” and all is fine. Yet, if I take for example the uncompiled script.as from asrun, I get:

 (0, 0) : ERR  : Unexpected end of file
asload: ../../source/as_restore.cpp:1673: void asCReader::ReadTypeDeclaration(asCTypeInfo*, int, bool*): Assertion `ot' failed.

The minimal sample I could create for this assertion is "\x00\x01" echoed into a file.

If I use the script.as from asbuild, it crashes without hitting an assertion due to a segfault:

─── Stack ──────────────────────────────────────────────────────────────
[0] from 0x00007ffff7b249bd in __GI___libc_free+29 at malloc.c:3093
[1] from 0x0000555555606906 in asCArray<int>::Allocate(unsigned int, bool)+508 at ../../source/as_array.h:267
[2] from 0x0000555555630b3d in asCArray<int>::Copy(int const*, unsigned int)+53 at ../../source/as_array.h:380
[3] from 0x000055555562c4e1 in asCArray<int>::operator=(asCArray<int> const&)+45 at ../../source/as_array.h:397
[4] from 0x000055555565e037 in asSTypeBehaviour::operator=(asSTypeBehaviour const&)+265 at ../../source/as_objecttype.h:51
[5] from 0x00005555556d8e20 in asCReader::ReadTypeDeclaration(asCTypeInfo*, int, bool*)+590 at ../../source/as_restore.cpp:1575
[6] from 0x00005555556d2fca in asCReader::ReadInner()+336 at ../../source/as_restore.cpp:175
[7] from 0x00005555556d2b1c in asCReader::Read(bool*)+46 at ../../source/as_restore.cpp:89
[8] from 0x00005555556bfd2a in asCModule::LoadByteCode(asIBinaryStream*, bool*)+252 at ../../source/as_module.cpp:1680
[9] from 0x00005555555afe14 in main(int, char**)+911 at ../../source/main.cpp:166

Btw, I was also wondering about asCReader::ReadData which seems to update the bytesRead to the requested size, irregardless of successful reading of the requested size bytes.

That's it, cheers

Thanks for providing the steps for reproducing the problems.

I'll review this and apply the necessary fixes so that angelscript reports a friendly error instead of crashing.

Regards,
Andreas

AngelCode.comΒ - game development and more - Reference DBΒ - game developer references
AngelScriptΒ - free scripting library -Β BMFontΒ - free bitmap font generator - TowerΒ - free puzzle game

This topic is closed to new replies.

Advertisement