From 3835427e97fb52b8a5745d904b662c571410cd30 Mon Sep 17 00:00:00 2001 From: Auke Kok Date: Wed, 27 Dec 2017 22:24:49 -0800 Subject: [PATCH] Initial checkin --- .luacheckrc | 8 ++++ depends.txt | 0 description.txt | 1 + init.lua | 92 ++++++++++++++++++++++++++++++++++++ mod.conf | 1 + readme.md | 123 ++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 225 insertions(+) create mode 100644 .luacheckrc create mode 100644 depends.txt create mode 100644 description.txt create mode 100644 init.lua create mode 100644 mod.conf create mode 100644 readme.md diff --git a/.luacheckrc b/.luacheckrc new file mode 100644 index 0000000..ae0fd83 --- /dev/null +++ b/.luacheckrc @@ -0,0 +1,8 @@ +unused_args = false +allow_defined_top = true + +read_globals = { + "minetest", + "SecureRandom", +} + diff --git a/depends.txt b/depends.txt new file mode 100644 index 0000000..e69de29 diff --git a/description.txt b/description.txt new file mode 100644 index 0000000..666abcd --- /dev/null +++ b/description.txt @@ -0,0 +1 @@ +Easier method for creating better and more secure formspecs. diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..7cd7ae3 --- /dev/null +++ b/init.lua @@ -0,0 +1,92 @@ + +--[[ + + FormSpec Context ('fsc') mod for minetest + + Copyright (C) 2018 Auke Kok + + Permission to use, copy, modify, and/or distribute this software for + any purpose with or without fee is hereby granted, provided that the + above copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY + SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +]]-- + +fsc = {} + +local _data = {} + +local SRNG = SecureRandom() +assert(SRNG) + +local function make_new_random_id() + local s = SRNG:next_bytes(16) + return s:gsub(".", function(c) return string.format("%02x", string.byte(c)) end) +end + +function fsc.show(name, formspec, context, callback) + assert(name) + assert(formspec) + assert(callback) + + if not context then + context = {} + end + + -- erase old context! + local id = "fsc:" .. make_new_random_id() + _data[name] = { + id = id, + name = name, + context = context, + callback = callback, + } + + minetest.show_formspec(name, id, formspec) +end + +minetest.register_on_player_receive_fields(function(player, formname, fields) + if not formname:match("fsc:") then + return false + end + + local name = player:get_player_name() + local data = _data[name] + if not data then + minetest.log("warning", "fsc: no data for formspec sent by " .. name) + minetest.close_formspec(name, formname) + return + end + if data.id ~= formname then + minetest.log("warning", "fsc: invalid id for formspec sent by " .. name) + minetest.close_formspec(name, formname) + _data[name] = nil + return + end + if data.name ~= name then + minetest.log("error", "fsc: possible hash collision or exploit (name mismatch)") + minetest.close_formspec(name, formname) + _data[name] = nil + return + end + if data then + if data.callback(player, fields, data.context) then + minetest.close_formspec(name, formname) + _data[name] = nil + elseif fields.quit then + _data[name] = nil + end + end +end) + +minetest.register_on_leaveplayer(function(player) + _data[player:get_player_name()] = nil +end) + diff --git a/mod.conf b/mod.conf new file mode 100644 index 0000000..d1c7940 --- /dev/null +++ b/mod.conf @@ -0,0 +1 @@ +name = fsc diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..c56b872 --- /dev/null +++ b/readme.md @@ -0,0 +1,123 @@ + +## fsc + +This mod is designed to help write more secure formspec handling +code. It achieves this by throwing out the concept of "formspec +names" entirely and giving each formspec shown to the player a unique, +random ID. The player can only then submit form data using this unique +ID, and, the handling code can invalidate the ID during processing +automatically. + +This reduces the risk that an attacker can forge formspec data and +send uninvited packets to the server. The server will discard any +form data that appears to come from a client that is attempting to +use old or incorrect fsc-created forms and will note this event in +the minetest log. + +Because of the simplicity of the approach, mods will no longer need +to focus on basic formspec handling code and can instead spent their +time verifying the proper permissions and input data correctness. + +This mod also provides a much more simple way to maintain a formspec +"context" and pass it along to subsequent formspecs. This makes +writing formspec code simpler as the context does not need to be +maintained outside the formspec handling or creation code, and no +memory leakage needs to be worried about. + +A player can also only ever obtain one context, and attempting to +use an invalid or outdated context will result in all current valid +formspec contexts being revoked. Combined together, all these features +make formspecs a lot safer to work with. + +## Usage + +The basic workflow of `fsc` contains of a single function call. Outside +of this function call, there are no other API functions or data. + +``` +function fsc.show(name, formspec, context, callback) + -- `name`: a playername, + -- `formspec`: a valid formspec string, + -- `context`: any data, may be `nil` + -- `callback`: function(player, fields, context). +``` + +The return value of `fsc.show()` is always `nil` - it returns nothing. + +The callback function will only be called if basic sanity checks on the data +pass requirements. You can implement it simply as follows: + +``` +local function callback(player, fields, context) + -- `player`: player object, + -- `fields`: table containing formspec data returned by the player client, + -- `context`: any data, will never be `nil`. If no context was passed + -- to `fsc.show()`, it will contain `{}` + return true +end +``` + +The return value of the callback may be `nil` or `true`. If you return +`nil`, the context is not invalidated, and the player may submit the +formspec using the same ID again. This is useful if the player merely +selects a list item or otherwise performs an action in the form that +does not cause the form to be closed on the client, and you wish to +keep the form open. + +If you return `true`, or if you return `nil` and `fields.quit` is set, +then the fsc code will invalidate the ID and close the formspec. You +should return `true` unless you want to keep the form open to the +player. + +Making a simple callback handler that shows a new form is therefore +relatively straightforward. The below example passes the current +context data through to the new form. The old form will close, and +the new form will appear to the player with the new content. + +``` +local function callback(player, fields, context) + local name = player:get_player_name() + if fields.rename then + fsc.show(name, + "field[new_name;What is the new name?;" .. minetest.formspec_escape(context.old_name) .. "]", + context, + callback) + return + else + -- do something else + return true + end +end +``` + +In some cases, you may wish to show a form without having the +need for a callback, in case the content is just informational and +non-interactive. In that case, you can omit a callback handler by +just inserting an empty callback handler, as follows: + + `fsc.show(name, formspec, {}, function() end)` + +## Node Formspecs + +Node formspecs are not handled. Due to the nature of node/inventory +formspecs, it is inherently impossible to perform the same checks on +node/inventory formspecs as `fsc` can do for (normal) formspecs. + +## License + +FormSpec Context ('fsc') mod for minetest, licensed under the `ISC` license: + + Copyright (C) 2018 Auke Kok + + Permission to use, copy, modify, and/or distribute this software for + any purpose with or without fee is hereby granted, provided that the + above copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY + SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +