diff --git a/doc/sscsm_api.md b/doc/sscsm_api.md new file mode 100644 index 000000000..600ce75ea --- /dev/null +++ b/doc/sscsm_api.md @@ -0,0 +1,212 @@ +# Server-sent client-side modding (SSCSM) API reference + +**Warning:** SSCSM is very experimental. The API will break. Always start your +mod with a version check (using `core.get_version()`). + +In SSCSM, the server sends scripts to the client, which it executes +client-side (in a sandbox, see also `sscsm_security.md`). +As modder, you can add these scripts to your server-side mod, and tell the engine +to send them. + +Please refer to `lua_api.md` for server-side modding. +(And refer to `client_lua_api.md` for client-provided client-side modding (CPCSM).) + + + +## Loading mods + +### Paths + +SSCSM uses a virtual file system (just a dictionary of virtual paths (strings)) +to file contents (strings). + +Each mod's files have paths of the form `modname:foo/bla.lua`. +Please don't rely on this, use `core.get_modpath()` instead. + +The virtual file paths within a mod are meant to mimic the filepaths on the +server, for example `/common/foo.lua` gets sent as `modname:common/foo.lua`. + +The engine loads `modname:init.lua` for all mods, in server mod dependency order. + +There is client and server builtin (modnames are `*client_builtin*` and +`*server_builtin*`). The server builtin is sent from the server, like any other +SSCSM, and the client builtin is located on the client. + + +### Mod sending API + +Currently, you can not add any mods. There's only a small hardcoded preview script +in C++ which is loaded when you set `enable_sscsm` to `singleplayer`. + + + +## API + +Unless noted otherwise, these work the same as in the server modding API. + +### Global callbacks + +* `core.register_globalstep(function(dtime))` + + +### SSCSM-specific API + +* `core.get_node_or_nil(pos)` +* `core.get_content_id(name)` +* `core.get_name_from_content_id(id)` + + +### Util API + +* `core.log([level,] text)` +* `core.get_us_time()` + * Limited in precision. +* `core.parse_json(str[, nullvalue])` +* `core.write_json(data[, styled])` +* `core.is_yes(arg)` +* `core.compress(data, method, ...)` +* `core.decompress(data, method, ...)` +* `core.encode_base64(string)` +* `core.decode_base64(string)` +* `core.get_version()` +* `core.sha1(string, raw)` +* `core.sha256(string, raw)` +* `core.colorspec_to_colorstring(colorspec)` +* `core.colorspec_to_bytes(colorspec)` +* `core.colorspec_to_table(colorspec)` +* `core.time_to_day_night_ratio(time_of_day)` +* `core.get_last_run_mod()` +* `core.set_last_run_mod(modname)` +* `core.urlencode(value)` + + +### Other + +* `core.get_current_modname()` +* `core.get_modpath(modname)` + + +### Builtin helpers + +* `math.*` additions + +* `vector.*` + +* `core.global_exists(name)` + +* `core.serialize(value)` +* `core.deserialize(str, safe)` + +* `dump2(obj, name, dumped)` +* `dump(obj, dumped)` +* `string.*` additions +* `table.*` additions +* `core.formspec_escape(text)` +* `core.hypertext_escape(text)` +* `core.wrap_text(str, limit, as_table)` +* `core.explode_table_event(evt)` +* `core.explode_textlist_event(evt)` +* `core.explode_scrollbar_event(evt)` +* `core.rgba(r, g, b, a)` +* `core.pos_to_string(pos, decimal_places)` +* `core.string_to_pos(value)` +* `core.string_to_area(value, relative_to)` +* `core.get_color_escape_sequence(color)` +* `core.get_background_escape_sequence(color)` +* `core.colorize(color, message)` +* `core.strip_foreground_colors(str)` +* `core.strip_background_colors(str)` +* `core.strip_colors(str)` +* `core.translate(textdomain, str, ...)` +* `core.translate_n(textdomain, str, str_plural, n, ...)` +* `core.get_translator(textdomain)` +* `core.pointed_thing_to_face_pos(placer, pointed_thing)` +* `core.string_to_privs(str, delim)` +* `core.privs_to_string(privs, delim)` +* `core.is_nan(number)` +* `core.parse_relative_number(arg, relative_to)` +* `core.parse_coordinates(x, y, z, relative_to)` + +* `core.inventorycube(img1, img2, img3)` +* `core.dir_to_facedir(dir, is6d)` +* `core.facedir_to_dir(facedir)` +* `core.dir_to_fourdir(dir)` +* `core.fourdir_to_dir(fourdir)` +* `core.dir_to_wallmounted(dir)` +* `core.wallmounted_to_dir(wallmounted)` +* `core.dir_to_yaw(dir)` +* `core.yaw_to_dir(yaw)` +* `core.is_colored_paramtype(ptype)` +* `core.strip_param2_color(param2, paramtype2)` + +* `core.after(time, func, ...)` + + +### Lua standard library + +* `assert` +* `collectgarbage` +* `error` +* `getfenv` +* `ipairs` +* `next` +* `pairs` +* `pcall` +* `rawequal` +* `rawget` +* `rawset` +* `select` +* `setfenv` +* `getmetatable` +* `setmetatable` +* `tonumber` +* `tostring` +* `type` +* `unpack` +* `_VERSION` +* `xpcall` +* `dofile` + * Overwritten. +* `load` + * Overwritten. +* `loadfile` + * Overwritten. +* `loadstring` + * Overwritten. +* `coroutine.*` +* `table.*` +* `math.*` +* `string.*` + * except `string.dump` +* `os.difftime` +* `os.time` +* `os.clock` + * Reduced precision. +* `debug.traceback` + + +### LuaJIT `jit` library + +* `jit.arch` +* `jit.flush` +* `jit.off` +* `jit.on` +* `jit.opt` +* `jit.os` +* `jit.status` +* `jit.version` +* `jit.version_num` + + +### Bit library + +* `bit.*` + + +### API only for client builtin + +* `core.get_builtin_path()` + * Returns path, depending on which builtin currently loads, or `nil`. +* `debug.getinfo(...)` +* `INIT` + * Is `"sscsm"`. diff --git a/doc/sscsm_security.md b/doc/sscsm_security.md new file mode 100644 index 000000000..acebad00d --- /dev/null +++ b/doc/sscsm_security.md @@ -0,0 +1,76 @@ +# SSCSM security + + +## Threat model + +* SSCSM scripts come from the server (potential malicious actor). We are the client. +* Authenticity of server is not given (our networking is not secure). So we have + to expect anyone who can send us UDP packets to the appropriate IP address to be + able to act on behalf of the server. +* The server may not tamper with, or get access to information of, anything besides + the stuff explicitly made accessible via the modding API (i.e. gameplay relevant + stuff, like map, node definitions, ...). + In particular, this excludes for (non-exhaustive) example files, file paths, + and settings. +* DOS is not an issue (as it is already easily possible to DOS a client). +* We already have an API via network packets (see `networkprotocol.h`). + This acts as upper bound: Every SSCSM API function could instead be a network + packet endpoint. There are no efforts to make SSCSM more secure than this. + + +## Non-binary `enable_sscsm` setting + +The `enable_sscsm` setting does not just allow en-/disabling SSCSM, it also allows +limiting on what sort of servers to enable SSCSM. Options are `nowhere`, `singleplayer`, +`localhost` (or singleplayer), `lan` (or lower), and everywhere. +On options `localhost` and lower, we know that (anyone who acts on the behalf of) +the server runs on the same machine, and the risk of it being malicious is pretty +much zero. + +Until sufficient security measures are in place, users are disallowed to set this +setting to anything higher than `localhost`. + + +## Lua sandbox + +* We execute only Lua scripts, in a Lua sandbox. +* See also `initializeSecuritySSCSM()`. +* We do not trust the Lua implementation to not have bugs. => Additional process + isolation layer as fallback. + + +## Process isolation + +* Not yet implemented. +* Separate SSCSM process. +* Sandboxing: + * Linux: Uses SECCOMP. + * ... (FIXME: write down stuff when you implement) + + +## Limit where we call into SSCSM + +* Even if the Lua sandbox and/or the process isolation are bug-free, the main + process client code can still be vulnerable. Consider this example: + * Client has an inventorylist A. + * User moves an item. + * SSCSM gets called (callback when item is moved). + * SSCSM can do anything now. It decides to delete A, then returns. + * Client still has reference to A on stack, tries to access it. + * => Use-after-free. +* To avoid these sort of issues, we only give control-flow to SSCSM in few special + places. + In particular, this includes packet handlers, and the client's `step()` function. +* In these places, the client already does not assume anything about the current + state (e.g. that an inventory exists). +* This makes sure that SSCSM API calls can also just happen in these places. + In packet handlers, the server can already cause arbitrary network API "calls" + to happen. Hence, new SSCSM API calls here do not lead to new vulnerabilities + that a network API would not cause as well. + + +## No precise clocks + +To mitigate time-based side-channel attacks, all available clock API functions +(`os.clock()` and `core.get_us_time()`) only have a precision of +`SSCSM_CLOCK_RESOLUTION_US` (20) us.