Skip to content
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

Access globals? #10

Open
TristonStuart opened this issue May 15, 2020 · 18 comments
Open

Access globals? #10

TristonStuart opened this issue May 15, 2020 · 18 comments

Comments

@TristonStuart
Copy link

Any way to access scope that executed jsc_eval?
Or the global scope?

@mbbill
Copy link
Owner

mbbill commented May 15, 2020

JSC >>> this
[object GlobalObject]

is that what you want?

@TristonStuart
Copy link
Author

I mean like javascript variables outside of the engine. Like if you have javascript code running outside the engine and you want a script to be able to access the variables outside of the engine.

@mbbill
Copy link
Owner

mbbill commented May 15, 2020

Oh I see. Right now it's not supported yet because it needs by-directional communication between the outer and inner JS environment across web assembly, but that's definitely a useful feature.

@TristonStuart
Copy link
Author

I'm trying to use this to obfuscate javascript. Is there any way to load in .jsc files directly? Like just straight javascript byte code?

@TristonStuart
Copy link
Author

TristonStuart commented May 15, 2020

Also how hard would it be for me to implement the jsc_eval code to access the normal js variables? Or do you know of any solutions that aren't that clean?

@mbbill
Copy link
Owner

mbbill commented May 16, 2020

There is a way to do that. Actually I already have some initial work done. You may take a look at https://github.com/mbbill/JSC.js/blob/master/Source/JavaScriptCore/JSCJS/jscjs.cpp#L229
You can find some examples of compiling Javascript code into bytecode and using jsc.js to load and run bytecode.

There are still some issues though. The bytecode compiler doesn't handle 'new' operator correctly. It's due to one of the limit of early implementation of JSC byte cache. I'm not sure if they have fixed it or not.

@TristonStuart
Copy link
Author

There is a way to do that. Actually I already have some initial work done. You may take a look at https://github.com/mbbill/JSC.js/blob/master/Source/JavaScriptCore/JSCJS/jscjs.cpp#L229
You can find some examples of compiling Javascript code into bytecode and using jsc.js to load and run bytecode.

There are still some issues though. The bytecode compiler doesn't handle 'new' operator correctly. It's due to one of the limit of early implementation of JSC byte cache. I'm not sure if they have fixed it or not.

Is there any way I can easily do that with the demo already setup? I just ripped the demo code and loaded in your jsc.wasm file for my own purposes. Does the demo js already support doing this or do I have to make modifications to it, or do I have to make modifications to the jsc.wasm code?

@mbbill
Copy link
Owner

mbbill commented May 16, 2020

https://github.com/mbbill/JSC.js/blob/master/build/BUILD.gn#L151
the jsc_compile and jsc_eval_bytecode is already exported. You can try to test them with your existing setup.

jsc_compile is to convert JS to bytecode. and jsc_eval_bytecode is to run the previously generated bytecode.

@TristonStuart
Copy link
Author

https://github.com/mbbill/JSC.js/blob/master/build/BUILD.gn#L151
the jsc_compile and jsc_eval_bytecode is already exported. You can try to test them with your existing setup.

jsc_compile is to convert JS to bytecode. and jsc_eval_bytecode is to run the previously generated bytecode.

How do I access them? I tried getCFunc and it only allows me to get "jsc_eval". Module only has "_jsc_eval".

@TristonStuart
Copy link
Author

As far as I can tell _jsc_compile and _jsc_eval_bytecode are not exported in the version used in your demo. I'll try to build it again but I still don't know why it won't work.

@raweden
Copy link

raweden commented May 23, 2020

static bindings between the WASM environment and the Native JS engine can be added by using the C based JSC API, if one are only adding a few endpoints which calls function or setter/getters from/to either side.

// implementation of the function taking the JS call at WASM side.
JSValueRef setTimeoutCallAsFunction(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) {
    printf("setTimeoutCallAsFunction called with no. args: %zu\n", argumentCount);
    
    ExecState* exec = toJS(ctx);
    VM& vm = exec->vm();
    
    if(argumentCount < 1){
        // throw not-enough-arguments-error.
        return JSValueMakeUndefined(ctx);
    }

    JSC::JSValue arg1 = toJS(exec, arguments[0]);

    if(!arg1.isFunction(vm) && !arg1.isString()){
        // throw TypeError here
        return JSValueMakeUndefined(ctx);
    }

    JSC::JSValue arg2 = argumentCount > 1 ? toJS(exec, arguments[1]) : jsNumber(0);

    int delay = arg2.toUInt32(exec);

    std::unique_ptr<WebCore::ScheduledAction> action = WebCore::ScheduledAction::create(exec->lexicalGlobalObject(), JSC::Strong<JSC::Unknown> { vm, JSC::asObject(arg1) });
    
    int timeoutId = jsc_proxy_setTimeout(0, delay);
    globalTimerMap().set(timeoutId, WTFMove(action));
    return JSValueMakeNumber(ctx, timeoutId);
}

void setupCommonGlobalScope(JSGlobalObject* globalObject){

    JSContextRef ctx = toRef(globalObject->globalExec());

    // globalThis.setTimeout(func, [delay], [arg1, arg2 ...])
    JSStringRef nameString = JSStringCreateWithUTF8CString("setTimeout");
    JSObjectRef setTimeoutFn = JSObjectMakeFunctionWithCallback(ctx, nameString, setTimeoutCallAsFunction);
    JSObjectSetProperty(ctx, toRef(globalObject), nameString, setTimeoutFn, kJSPropertyAttributeNone, nullptr);
    JSStringRelease(nameString);
}

JSGlobalObject* jsc_global() {
    //jsc_init();
    //static VM& vm = VM::create(LargeHeap).leakRef();
    //JSLockHolder locker(vm);
    VM& vm = globalVM();
    
    static JSGlobalObject* globalObject = JSGlobalObject::create(vm, JSGlobalObject::createStructure(vm, jsNull()));
    globalObject->setRemoteDebuggingEnabled(true);

    JSContextRef ctx = toRef(globalObject->globalExec());

    // setting the name of the context (visable to inspector API)
    vm.vmEntryGlobalObject(globalObject->globalExec())->setName(String("default:jsc_global"));

    // Common Features in Global Scope (globalThis)
    setupCommonGlobalScope(globalObject);

    // console API, compile with `ENABLE_REMOTE_INSPECTOR=1` and uses custom RemoteInspector subclass, to bridge it to WASM host.
    static unique_ptr<RemoteConnectionToTarget> l_connectionToTarget = make_unique<RemoteConnectionToTarget>(globalObject->inspectorDebuggable());
    RemoteConnectionToTarget* connectionToTarget = l_connectionToTarget.get();
    connectionToTarget->setup(false, false);
    
    globalScriptContextMap().set(String("default"), globalObject);
    return globalObject;
}

Another approach is a fit all use cases; it should be possible to implement a custom VM wrapper, using the same wrapper protocol used to wrap Objective-C classes/object and binds those into the JS environment. This one would require a fair share of work. See the .mm files in /Source/JSC/API, glib GTK bindings also uses these API to bind classes/objects into the JS environment.

Another approach is to implement these types of bindings are to use proxy object which allows to respond with callback respond to any action applied to a object get/set/delete/has implement private endpoints in both environments and use a hash map to look these actions up, encoding/decoding primitives, reference objects in a hash map and wrapper callback/function the same way.

The hardest challenge with any approach is to make them work with the Garbage Collection at the hosting/native side.

@TristonStuart
Copy link
Author

Interesting stuff. Thanks for the ideas and code. I will try to play around with this as soon as I can actually get this to compile. But for some reason it refuses on the linking JSC process at the end.

@raweden
Copy link

raweden commented May 24, 2020

@TristonStuart It took some time getting the settings right for the build to get successful.. From those errors you posted in a previous issue, it seams like the ALWAYS_INLINE compile time defined is not recognised properly.

Add this line

        "ALWAYS_INLINE=inline",

into the file /JSC.js/build/BUILD.gn into the end of the defines in the section seen below:

config("compiler_defaults") {
    defines = [
        "JSCJS=1",
        "ENABLE_JIT=0",

There is a few more compile time defines that could be added (unrelated to your error):

        "ENABLE_WEBASSEMBLY=0",
        "USE_GENERIC_EVENT_LOOP=1",
        "ENABLE_INSPECTOR_ALTERNATE_DISPATCHERS=0",
        "ENABLE_REMOTE_INSPECTOR=0,
        "LOG_DISABLED=0",
        "ENABLE_DEVELOPER_MODE=1",
        "HAVE_QOS_CLASSES=0", 

ENABLE_WEBASSEMBLY makes no sense to emulate WASM in WASM runtime.
USE_GENERIC_EVENT_LOOP controls the implementation of application runloop which is defined within the WTF (WebKit Template Framework).
ENABLE_REMOTE_INSPECTOR controls the behavior of the inspector, the default JSC.js code base don't have any implementation in place to use it, the code base controlled by the define is what enables for example console.log and the DevTools debugger (with breakpoints etc) seen within jsc/safari to work.
ENABLE_INSPECTOR_ALTERNATE_DISPATCHERS is a sub settings for Remote Inspector.
LOG_DISABLED=0 enables some log statements.
ENABLE_DEVELOPER_MODE=1 more info in exceptions.
HAVE_QOS_CLASSES

@raweden
Copy link

raweden commented May 24, 2020

Just as a Note in my example code above I where using WebCore::ScheduledAction which is not included within JSC.js by default. It has to be copied from the WebKit repo and be slightly modified to be independent from the rest of the WebCore library.

I plan on releasing the code written to bridge/implement the common minimum global scope, including Fetch API with streams somewhere in the future.

@TristonStuart
Copy link
Author

@TristonStuart It took some time getting the settings right for the build to get successful.. From those errors you posted in a previous issue, it seams like the ALWAYS_INLINE compile time defined is not recognised properly.

Add this line

        "ALWAYS_INLINE=inline",

into the file /JSC.js/build/BUILD.gn into the end of the defines in the section seen below:

config("compiler_defaults") {
    defines = [
        "JSCJS=1",
        "ENABLE_JIT=0",

There is a few more compile time defines that could be added (unrelated to your error):

        "ENABLE_WEBASSEMBLY=0",
        "USE_GENERIC_EVENT_LOOP=1",
        "ENABLE_INSPECTOR_ALTERNATE_DISPATCHERS=0",
        "ENABLE_REMOTE_INSPECTOR=0,
        "LOG_DISABLED=0",
        "ENABLE_DEVELOPER_MODE=1",
        "HAVE_QOS_CLASSES=0", 

ENABLE_WEBASSEMBLY makes no sense to emulate WASM in WASM runtime.
USE_GENERIC_EVENT_LOOP controls the implementation of application runloop which is defined within the WTF (WebKit Template Framework).
ENABLE_REMOTE_INSPECTOR controls the behavior of the inspector, the default JSC.js code base don't have any implementation in place to use it, the code base controlled by the define is what enables for example console.log and the DevTools debugger (with breakpoints etc) seen within jsc/safari to work.
ENABLE_INSPECTOR_ALTERNATE_DISPATCHERS is a sub settings for Remote Inspector.
LOG_DISABLED=0 enables some log statements.
ENABLE_DEVELOPER_MODE=1 more info in exceptions.
HAVE_QOS_CLASSES

This works perfectly. The code was able to compile and run perfectly. Thank you so much. I am looking forward to seeing you add global access as I think this project could be used for js security. I would make a pull request and add those changes.

@TristonStuart
Copy link
Author

Just as a Note in my example code above I where using WebCore::ScheduledAction which is not included within JSC.js by default. It has to be copied from the WebKit repo and be slightly modified to be independent from the rest of the WebCore library.

I plan on releasing the code written to bridge/implement the common minimum global scope, including Fetch API with streams somewhere in the future.

Have you made a working prototype?

@raweden
Copy link

raweden commented May 28, 2020

Have you made a working prototype?

Its about halfway implemented, written in c++ for performance reasons. I looked at merge these parts from WebCore, but its hard work as its hard to just pull out certain parts of from that code base, and these JavaScript API written for WebCore part of Webkit is also written in predefined JS, which in native Safari don't matter as it JIT compiles it anyways, this is not possible in JSC which requires a different approach of implementing it to squeeze out the most performance.

@TristonStuart
Copy link
Author

It sounds very complicated. If you make a working version I would love to include it in the wrapper API I wrote. I think this project can go a long way to help protect javascript code security.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants