From 38063812b6e931740f9d9d7b4bc95429dcfb3aa6 Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Mon, 7 Mar 2022 10:35:23 -0800 Subject: [PATCH] zet.tlv: switch file writes to new API The interface for apps looks much nicer now, see 'main' in zet.tlv. However there are some open issues: - It can still be confusing to the computer owner that an app tries to write to some temporary file that isn't mentioned anywhere. - File renames can fail if /tmp is on a different volume. - What happens if an app overrides start_writing()? The computer owner may think they've audited the caller of start_writing and give it blanket file permissions. Teliva tunnels through start_writing when computing the caller. If the app can control what start_writing does, the app could be performing arbitrary malicious file operations. Right now things actually seem perfectly secure. Overriding start_writing has no effect. Our approach for loading .tlv files (in reverse chronological order, preventing older versions from overriding newer ones) has the accidentally _great_ property that Teliva apps can never override system definitions. So we have a new reason to put standard libraries in a .lua file: if we need to prevent apps from overriding it. This feels like something that needs an automated test, both to make sure I'm running the right experiment and to ensure I don't accidentally cause a regression in the future. I can totally imagine a future rewrite that tried a different approach than reverse-chronological. --- src/liolib.c | 14 ++++++++++++++ src/teliva.c | 11 +++++++++++ src/teliva.h | 2 ++ zet.tlv | 37 +++++++++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+) diff --git a/src/liolib.c b/src/liolib.c index 45d40a9..a1a5b35 100644 --- a/src/liolib.c +++ b/src/liolib.c @@ -122,6 +122,11 @@ static int io_tostring (lua_State *L) { } +static int is_equal(const char *a, const char *b) { + return strcmp(a, b) == 0; +} + + static char iolib_errbuf[1024] = {0}; static int io_open (lua_State *L) { const char *filename = luaL_checkstring(L, 1); @@ -134,6 +139,15 @@ static int io_open (lua_State *L) { const char *caller = get_caller(L); if (file_operation_permitted(caller, filename, mode)) *pf = fopen(filename, mode); + else if (is_equal(caller, "start_writing") || is_equal(caller, "start_reading")) { + caller = get_caller_of_caller(L); + if (file_operation_permitted(caller, filename, mode)) + *pf = fopen(filename, mode); + else { + snprintf(iolib_errbuf, 1024, "app tried to open file '%s' from caller '%s'; adjust its permissions (ctrl-p) if that is expected", filename, caller); + Previous_message = iolib_errbuf; + } + } else { snprintf(iolib_errbuf, 1024, "app tried to open file '%s' from caller '%s'; adjust its permissions (ctrl-p) if that is expected", filename, caller); Previous_message = iolib_errbuf; diff --git a/src/teliva.c b/src/teliva.c index 71576b5..db59428 100644 --- a/src/teliva.c +++ b/src/teliva.c @@ -337,6 +337,17 @@ char* get_caller(lua_State* L) { return result; } +char* get_caller_of_caller(lua_State* L) { + static char result[1024] = {0}; + lua_Debug ar; + lua_getstack(L, 2, &ar); + lua_getinfo(L, "n", &ar); + memset(result, '\0', 1024); + if (ar.name) + strncpy(result, ar.name, 1020); + return result; +} + void save_caller_as(lua_State* L, const char* name, const char* caller_name) { // push table of caller tables luaL_newmetatable(L, "__teliva_caller"); diff --git a/src/teliva.h b/src/teliva.h index db2138d..e322aa3 100644 --- a/src/teliva.h +++ b/src/teliva.h @@ -168,6 +168,8 @@ extern void assign_call_graph_depth_to_name(lua_State* L, int depth, const char* extern char* get_caller(lua_State* L); extern void save_caller(lua_State* L, const char* name, int call_graph_depth); extern void draw_callers_of_current_definition(lua_State* L); +extern char* get_caller_of_caller(lua_State* L); + extern void append_to_audit_log(lua_State* L, const char* buffer); /* Standard UI elements */ diff --git a/zet.tlv b/zet.tlv index e9e08cc..1963796 100644 --- a/zet.tlv +++ b/zet.tlv @@ -3593,3 +3593,40 @@ >function read_zettels(infile) > zettels = jsonf.decode(infile) >end +- __teliva_timestamp: + >Mon Mar 7 10:31:27 2022 + main: + >function main() + > init_colors() + > curses.curs_set(0) -- hide cursor except when editing + > + > -- load any saved zettels + > local infile = start_reading(nil, 'zet') + > if infile then + > read_zettels(infile) + > end + > current_zettel_id = zettels.root -- cursor + > view_settings.first_zettel = zettels.root -- start rendering here + > + > while true do + > render(Window) + > update(Window) + > + > -- save zettels + > local outfile = start_writing(nil, 'zet') + > if outfile then + > write_zettels(outfile) + > end + > -- TODO: what if io.open failed for a non-sandboxing related reason?! + > -- We could silently fail to save. + > end + >end +- __teliva_timestamp: + >Mon Mar 7 10:32:08 2022 + __teliva_note: + >switch to new file API for writing + write_zettels: + >function write_zettels(outfile) + > outfile:send(json.encode(zettels)) + > outfile:close() + >end