Running a Neko Module in a Sandbox

It is sometimes necessary to dynamically load and run untrusted neko modules, such as server plugins. HaXe makes it possible to do this without making your server vulnerable to malicious code. This is done by restricting the library functions untrusted modules can call. The restrictions only affect the untrusted modules, and only prevents it from calling library functions you specify.

In this tutorial we'll create an untrusted module that calls two library functions, neko.FileSystem.exists and neko.FileSystem.deleteFile. Then we'll create a main program that dynamically loads and runs the module in a sandbox which allows it to call exists, but prevents it from calling deleteFile.

Create the Untrusted Module


The untrusted module, Plugin.hx:
// file Plugin.hx
class Plugin {
    public function new() {
    }

    public function checkFile(fname) {
        if (neko.FileSystem.exists(fname))
            neko.Lib.println(fname + " exists");
        else
            neko.Lib.println(fname + " does not exist");
    }

    public function deleteFile(fname) {
        neko.FileSystem.deleteFile(fname);
    }
}

The checkFile method uses the "exists" library function to check if a file exists. The deleteFile method calls the "deleteFile" library function to delete a file. Compile the module with:

haxe -neko plugin.n Plugin

Create the Server Application


Create Main.hx in another directory. Copy plugin.n to the new directory.
// file Main.hx
import neko.vm.Loader;
import neko.vm.Module;

class Main {
    static function main() {
        var outOfSandbox = function(f:Array<Dynamic>):Dynamic {
            throw "sandbox exception";
        }

        var loadPrimitive = function(name:String, nargs:Int):Dynamic {
            if (name == "std@file_delete")
                return Reflect.makeVarArgs(outOfSandbox);
            return Loader.local().loadPrimitive(name, nargs);
        }

        var safeLoader = Loader.make(loadPrimitive, Loader.local().loadModule);
        var module = safeLoader.loadModule("plugin");
        var classes : Dynamic = module.exportsTable().__classes;
        var plugin = Type.createInstance(classes.Plugin, []);
        
        try {
            plugin.checkFile("testfile.txt");
            plugin.deleteFile("testfile.txt");
        }
        catch (ex:String) {
            neko.Lib.println("exception: " + ex);
        }
        plugin.checkFile("testfile.txt");
    }
}

The main program defines two functions. outOfSandbox simply throws an exception. It will be called in place of library functions that we've decided the module should not call. loadPrimitive returns the desired function if it is allowed, otherwise it returns the outOfSandbox function.

Then we create a custom loader using the loadPrimitive we defined and use it to load the "plugin" module. The module loading code above is based on an example from the book "Professional HaXe and Neko." Once we have the module, we call its methods to test the sandbox.

Compile with:

haxe -neko main.n -main Main

Create the file "testfile.txt" in the same directory. It's contents don't matter.

Run the Test


Run with:
neko Main

The output should resemble:

testfile.txt exists
exception: sandbox exception
testfile.txt exists

The first line shows that the first call to "exists" was successful and that the file exists. The second line shows that the sandbox exception was thrown in the "deleteFile" call. The third line shows that the "deleteFile" library function wasn't called, since the file still exists. hazzah!

version #7949, modified 2010-01-23 21:14:07 by TheHippo