From acf9f456e41a9fd02b26460e356e7b622e73dba3 Mon Sep 17 00:00:00 2001 From: Tom Fitzhenry Date: Mon, 24 Jun 2024 19:24:33 +1000 Subject: [PATCH] config: try to parse as ProtoJSON JSON is an easier to generate than TextProto in some environments, e.g. Jsonnet or Nix. File I/O had to be reworked to support the json_util API: https://protobuf.dev/reference/cpp/api-docs/google.protobuf.util.json_util/ --- config.cc | 60 +++++++--- configs/bash-with-fake-geteuid.json | 170 ++++++++++++++++++++++++++++ 2 files changed, 216 insertions(+), 14 deletions(-) create mode 100644 configs/bash-with-fake-geteuid.json diff --git a/config.cc b/config.cc index 077c1b9..77dbc3a 100644 --- a/config.cc +++ b/config.cc @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -32,6 +33,7 @@ #include #include +#include #include #include @@ -302,39 +304,69 @@ static bool parseInternal(nsjconf_t* nsjconf, const nsjail::NsJailConfig& njc) { return true; } +static std::list error_messages; + static void logHandler( google::protobuf::LogLevel level, const char* filename, int line, const std::string& message) { - LOG_W("config.cc: '%s'", message.c_str()); + error_messages.push_back(message); +} + +static void flushLog() { + for (auto message : error_messages) { + LOG_W("config.cc: '%s'", message.c_str()); + } + error_messages.clear(); } bool parseFile(nsjconf_t* nsjconf, const char* file) { LOG_D("Parsing configuration from '%s'", file); - int fd = TEMP_FAILURE_RETRY(open(file, O_RDONLY | O_CLOEXEC)); - if (fd == -1) { + std::ifstream ifs(file); + if (!ifs.is_open()) { PLOG_W("Couldn't open config file '%s'", file); return false; } - google::protobuf::SetLogHandler(logHandler); - google::protobuf::io::FileInputStream input(fd); - input.SetCloseOnDelete(true); + std::string conf((std::istreambuf_iterator(ifs)), (std::istreambuf_iterator())); /* Use static so we can get c_str() pointers, and copy them into the nsjconf struct */ - static nsjail::NsJailConfig nsc; + static nsjail::NsJailConfig json_nsc; + static nsjail::NsJailConfig text_nsc; + + google::protobuf::SetLogHandler(logHandler); + auto json_status = google::protobuf::util::JsonStringToMessage(conf, &json_nsc); + bool text_parsed = google::protobuf::TextFormat::ParseFromString(conf, &text_nsc); - auto parser = google::protobuf::TextFormat::Parser(); - if (!parser.Parse(&input, &nsc)) { - LOG_W("Couldn't parse file '%s' from Text into ProtoBuf", file); + if (json_status.ok() && text_parsed) { + LOG_W("Config file '%s' ambiguously parsed as TextProto and ProtoJSON", file); return false; } - if (!parseInternal(nsjconf, nsc)) { - LOG_W("Couldn't parse the ProtoBuf from '%s'", file); + + if (!json_status.ok() && !text_parsed) { + LOG_W("Config file '%s' failed to parse as either TextProto or ProtoJSON", file); + flushLog(); + LOG_W("config.cc: ProtoJSON parse status: '%s'", json_status.ToString().c_str()); return false; } - LOG_D("Parsed config from '%s':\n'%s'", file, nsc.DebugString().c_str()); - return true; + if (json_status.ok() && !text_parsed) { + if (!parseInternal(nsjconf, json_nsc)) { + LOG_W("Couldn't parse the ProtoJSON from '%s'", file); + return false; + } + LOG_D("Parsed JSON config from '%s':\n'%s'", file, json_nsc.DebugString().c_str()); + return true; + } + + if (text_parsed && !json_status.ok()) { + if (!parseInternal(nsjconf, text_nsc)) { + LOG_W("Couldn't parse the TextProto from '%s'", file); + return false; + } + LOG_D("Parsed TextProto config from '%s':\n'%s'", file, text_nsc.DebugString().c_str()); + return true; + } + return false; } } // namespace config diff --git a/configs/bash-with-fake-geteuid.json b/configs/bash-with-fake-geteuid.json new file mode 100644 index 0000000..1a06522 --- /dev/null +++ b/configs/bash-with-fake-geteuid.json @@ -0,0 +1,170 @@ +{ + "name": "bash-with-fake-geteuid", + "description": [ + "An example/demo policy which allows to execute /bin/bash and other commands in ", + "a fairly restricted jail containing only some directories from the main ", + "system, and with blocked __NR_syslog syscall. Also, __NR_geteuid returns -1337 ", + "value, which /usr/bin/id will show as euid=4294965959, and ptrace is blocked ", + "but returns success, hence strange behavior of the strace command. ", + "This is an example/demo policy, hence it repeats many default values from the ", + "https://github.com/google/nsjail/blob/master/config.proto PB schema " + ], + "mode": "ONCE", + "hostname": "JAILED-BASH", + "cwd": "/tmp", + "port": 31337, + "bindhost": "127.0.0.1", + "maxConnsPerIp": 10, + "timeLimit": 100, + "daemon": false, + "maxCpus": 1, + "keepEnv": false, + "envar": [ + "ENVAR1=VALUE1", + "ENVAR2=VALUE2", + "TERM=linux", + "HOME=/", + "PS1=[\\H:\\t:\\s-\\V:\\w]\\$ " + ], + "keepCaps": true, + "cap": [ + "CAP_NET_ADMIN", + "CAP_NET_RAW" + ], + "silent": false, + "skipSetsid": true, + "stderrToNull": false, + "passFd": [ + 100, + 3 + ], + "disableNoNewPrivs": false, + "rlimitAs": "128", + "rlimitCore": "0", + "rlimitCpu": "10", + "rlimitFsize": "0", + "rlimitNofile": "32", + "rlimitNprocType": "SOFT", + "rlimitStackType": "SOFT", + "personaAddrCompatLayout": false, + "personaMmapPageZero": false, + "personaReadImpliesExec": false, + "personaAddrLimit3gb": false, + "personaAddrNoRandomize": false, + "cloneNewnet": true, + "cloneNewuser": true, + "cloneNewns": true, + "cloneNewpid": true, + "cloneNewipc": true, + "cloneNewuts": true, + "cloneNewcgroup": true, + "uidmap": [ + { + "insideId": "0", + "outsideId": "", + "count": 1 + } + ], + "gidmap": [ + { + "insideId": "0", + "outsideId": "", + "count": 1 + } + ], + "mountProc": false, + "mount": [ + { + "src": "/lib", + "dst": "/lib", + "isBind": true, + "rw": false + }, + { + "src": "/bin", + "dst": "/bin", + "isBind": true, + "rw": false + }, + { + "src": "/sbin", + "dst": "/sbin", + "isBind": true, + "rw": false + }, + { + "src": "/usr", + "dst": "/usr", + "isBind": true, + "rw": false + }, + { + "src": "/lib64", + "dst": "/lib64", + "isBind": true, + "rw": false, + "mandatory": false + }, + { + "src": "/lib32", + "dst": "/lib32", + "isBind": true, + "rw": false, + "mandatory": false + }, + { + "dst": "/tmp", + "fstype": "tmpfs", + "isBind": false, + "rw": true, + "nosuid": true, + "nodev": true, + "noexec": true + }, + { + "src": "/dev/null", + "dst": "/dev/null", + "isBind": true, + "rw": true + }, + { + "dst": "/proc", + "fstype": "proc", + "rw": false + }, + { + "srcContent": "VGhpcyBmaWxlIHdhcyBjcmVhdGVkIGR5bmFtaWNhbGx5", + "dst": "/DYNAMIC_FILE" + }, + { + "src": "/nonexistent_777", + "dst": "/nonexistent_777", + "isBind": true, + "mandatory": false + }, + { + "src": "/proc/self/fd", + "dst": "/dev/fd", + "isSymlink": true + }, + { + "src": "/some/unimportant/target", + "dst": "/proc/no/symlinks/can/be/created/in/proc", + "mandatory": false, + "isSymlink": true + } + ], + "seccompString": [ + "ERRNO(1337) { geteuid }\t", + "ERRNO(1) { ptrace, sched_setaffinity }\t\t", + "KILL_PROCESS { syslog }\t\t", + "DEFAULT ALLOW\t\t\t" + ], + "execBin": { + "path": "/bin/bash", + "arg": [ + "-i" + ], + "arg0": "sh" + } +}