1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-08-01 17:38:41 +00:00

add API and security doc

This commit is contained in:
Desour 2025-03-25 16:20:09 +01:00
parent 58e5d0b4ff
commit ff1cb8f9df
2 changed files with 288 additions and 0 deletions

212
doc/sscsm_api.md Normal file
View file

@ -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 `<modpath>/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"`.

76
doc/sscsm_security.md Normal file
View file

@ -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.