Skip to content

Commit

Permalink
config: try to parse as ProtoJSON
Browse files Browse the repository at this point in the history
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/
  • Loading branch information
tomfitzhenry committed Jun 25, 2024
1 parent a00a0ef commit acf9f45
Show file tree
Hide file tree
Showing 2 changed files with 216 additions and 14 deletions.
60 changes: 46 additions & 14 deletions config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <fcntl.h>
#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <google/protobuf/text_format.h>
#include <google/protobuf/util/json_util.h>
#include <stdio.h>
#include <sys/mount.h>
#include <sys/personality.h>
Expand All @@ -32,6 +33,7 @@
#include <sys/types.h>

#include <fstream>
#include <list>
#include <string>
#include <vector>

Expand Down Expand Up @@ -302,39 +304,69 @@ static bool parseInternal(nsjconf_t* nsjconf, const nsjail::NsJailConfig& njc) {
return true;
}

static std::list<std::string> 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<char>(ifs)), (std::istreambuf_iterator<char>()));

/* 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
170 changes: 170 additions & 0 deletions configs/bash-with-fake-geteuid.json
Original file line number Diff line number Diff line change
@@ -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"
}
}

0 comments on commit acf9f45

Please sign in to comment.