From 3a9d64b8e354176b41936b2b9e1d3b20fe0290f2 Mon Sep 17 00:00:00 2001 From: Philipp Otterbein Date: Sat, 7 Dec 2024 20:46:11 +0100 Subject: [PATCH] fromJSON/fromTOML: throw if string contains null byte --- src/libexpr/eval.cc | 9 +++++++++ src/libexpr/json-to-value.cc | 2 ++ src/libexpr/primops/fromTOML.cc | 14 +++++++++++--- src/libexpr/value.hh | 2 ++ .../eval-fail-fromJSON-keyWithNullByte.err.exp | 8 ++++++++ .../lang/eval-fail-fromJSON-keyWithNullByte.nix | 1 + .../eval-fail-fromJSON-valueWithNullByte.err.exp | 8 ++++++++ .../lang/eval-fail-fromJSON-valueWithNullByte.nix | 1 + .../eval-fail-fromTOML-keyWithNullByte.err.exp | 8 ++++++++ .../lang/eval-fail-fromTOML-keyWithNullByte.nix | 1 + .../eval-fail-fromTOML-valueWithNullByte.err.exp | 8 ++++++++ .../lang/eval-fail-fromTOML-valueWithNullByte.nix | 1 + 12 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 tests/functional/lang/eval-fail-fromJSON-keyWithNullByte.err.exp create mode 100644 tests/functional/lang/eval-fail-fromJSON-keyWithNullByte.nix create mode 100644 tests/functional/lang/eval-fail-fromJSON-valueWithNullByte.err.exp create mode 100644 tests/functional/lang/eval-fail-fromJSON-valueWithNullByte.nix create mode 100644 tests/functional/lang/eval-fail-fromTOML-keyWithNullByte.err.exp create mode 100644 tests/functional/lang/eval-fail-fromTOML-keyWithNullByte.nix create mode 100644 tests/functional/lang/eval-fail-fromTOML-valueWithNullByte.err.exp create mode 100644 tests/functional/lang/eval-fail-fromTOML-valueWithNullByte.nix diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 05f58957e..7b6e89534 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -3178,5 +3178,14 @@ std::ostream & operator << (std::ostream & str, const ExternalValueBase & v) { return v.print(str); } +void forceNoNullByte(std::string_view s) +{ + if (s.find('\0') != s.npos) { + using namespace std::string_view_literals; + auto str = replaceStrings(std::string(s), "\0"sv, "␀"sv); + throw Error("input string '%s' cannot be represented as Nix string because it contains null bytes", str); + } +} + } diff --git a/src/libexpr/json-to-value.cc b/src/libexpr/json-to-value.cc index 9ac56541a..17cab7ad5 100644 --- a/src/libexpr/json-to-value.cc +++ b/src/libexpr/json-to-value.cc @@ -50,6 +50,7 @@ class JSONSax : nlohmann::json_sax { public: void key(string_t & name, EvalState & state) { + forceNoNullByte(name); attrs.insert_or_assign(state.symbols.create(name), &value(state)); } }; @@ -122,6 +123,7 @@ public: bool string(string_t & val) override { + forceNoNullByte(val); rs->value(state).mkString(val); rs->add(); return true; diff --git a/src/libexpr/primops/fromTOML.cc b/src/libexpr/primops/fromTOML.cc index 264046711..404425054 100644 --- a/src/libexpr/primops/fromTOML.cc +++ b/src/libexpr/primops/fromTOML.cc @@ -28,8 +28,10 @@ static void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, V auto attrs = state.buildBindings(size); - for(auto & elem : table) + for(auto & elem : table) { + forceNoNullByte(elem.first); visit(attrs.alloc(elem.first), elem.second); + } v.mkAttrs(attrs); } @@ -54,7 +56,11 @@ static void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, V v.mkFloat(toml::get(t)); break;; case toml::value_t::string: - v.mkString(toml::get(t)); + { + auto s = toml::get(t); + forceNoNullByte(s); + v.mkString(s); + } break;; case toml::value_t::local_datetime: case toml::value_t::offset_datetime: @@ -66,7 +72,9 @@ static void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, V attrs.alloc("_type").mkString("timestamp"); std::ostringstream s; s << t; - attrs.alloc("value").mkString(toView(s)); + auto str = toView(s); + forceNoNullByte(str); + attrs.alloc("value").mkString(str); v.mkAttrs(attrs); } else { throw std::runtime_error("Dates and times are not supported"); diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh index d98161488..88fcae986 100644 --- a/src/libexpr/value.hh +++ b/src/libexpr/value.hh @@ -510,4 +510,6 @@ typedef std::shared_ptr RootValue; RootValue allocRootValue(Value * v); +void forceNoNullByte(std::string_view s); + } diff --git a/tests/functional/lang/eval-fail-fromJSON-keyWithNullByte.err.exp b/tests/functional/lang/eval-fail-fromJSON-keyWithNullByte.err.exp new file mode 100644 index 000000000..a16192c59 --- /dev/null +++ b/tests/functional/lang/eval-fail-fromJSON-keyWithNullByte.err.exp @@ -0,0 +1,8 @@ +error: + … while calling the 'fromJSON' builtin + at /pwd/lang/eval-fail-fromJSON-keyWithNullByte.nix:1:1: + 1| builtins.fromJSON ''{"a\u0000b": 1}'' + | ^ + 2| + + error: input string 'a␀b' cannot be represented as Nix string because it contains null bytes diff --git a/tests/functional/lang/eval-fail-fromJSON-keyWithNullByte.nix b/tests/functional/lang/eval-fail-fromJSON-keyWithNullByte.nix new file mode 100644 index 000000000..ffaa6a97d --- /dev/null +++ b/tests/functional/lang/eval-fail-fromJSON-keyWithNullByte.nix @@ -0,0 +1 @@ +builtins.fromJSON ''{"a\u0000b": 1}'' diff --git a/tests/functional/lang/eval-fail-fromJSON-valueWithNullByte.err.exp b/tests/functional/lang/eval-fail-fromJSON-valueWithNullByte.err.exp new file mode 100644 index 000000000..c5c08e6ff --- /dev/null +++ b/tests/functional/lang/eval-fail-fromJSON-valueWithNullByte.err.exp @@ -0,0 +1,8 @@ +error: + … while calling the 'fromJSON' builtin + at /pwd/lang/eval-fail-fromJSON-valueWithNullByte.nix:1:1: + 1| builtins.fromJSON ''"a\u0000b"'' + | ^ + 2| + + error: input string 'a␀b' cannot be represented as Nix string because it contains null bytes diff --git a/tests/functional/lang/eval-fail-fromJSON-valueWithNullByte.nix b/tests/functional/lang/eval-fail-fromJSON-valueWithNullByte.nix new file mode 100644 index 000000000..c71ab990d --- /dev/null +++ b/tests/functional/lang/eval-fail-fromJSON-valueWithNullByte.nix @@ -0,0 +1 @@ +builtins.fromJSON ''"a\u0000b"'' diff --git a/tests/functional/lang/eval-fail-fromTOML-keyWithNullByte.err.exp b/tests/functional/lang/eval-fail-fromTOML-keyWithNullByte.err.exp new file mode 100644 index 000000000..dc2180f0b --- /dev/null +++ b/tests/functional/lang/eval-fail-fromTOML-keyWithNullByte.err.exp @@ -0,0 +1,8 @@ +error: + … while calling the 'fromTOML' builtin + at /pwd/lang/eval-fail-fromTOML-keyWithNullByte.nix:1:1: + 1| builtins.fromTOML ''"a\u0000b" = 1'' + | ^ + 2| + + error: while parsing TOML: error: input string 'a␀b' cannot be represented as Nix string because it contains null bytes diff --git a/tests/functional/lang/eval-fail-fromTOML-keyWithNullByte.nix b/tests/functional/lang/eval-fail-fromTOML-keyWithNullByte.nix new file mode 100644 index 000000000..b622dc4dc --- /dev/null +++ b/tests/functional/lang/eval-fail-fromTOML-keyWithNullByte.nix @@ -0,0 +1 @@ +builtins.fromTOML ''"a\u0000b" = 1'' diff --git a/tests/functional/lang/eval-fail-fromTOML-valueWithNullByte.err.exp b/tests/functional/lang/eval-fail-fromTOML-valueWithNullByte.err.exp new file mode 100644 index 000000000..0235692a8 --- /dev/null +++ b/tests/functional/lang/eval-fail-fromTOML-valueWithNullByte.err.exp @@ -0,0 +1,8 @@ +error: + … while calling the 'fromTOML' builtin + at /pwd/lang/eval-fail-fromTOML-valueWithNullByte.nix:1:1: + 1| builtins.fromTOML ''k = "a\u0000b"'' + | ^ + 2| + + error: while parsing TOML: error: input string 'a␀b' cannot be represented as Nix string because it contains null bytes diff --git a/tests/functional/lang/eval-fail-fromTOML-valueWithNullByte.nix b/tests/functional/lang/eval-fail-fromTOML-valueWithNullByte.nix new file mode 100644 index 000000000..183cab6b3 --- /dev/null +++ b/tests/functional/lang/eval-fail-fromTOML-valueWithNullByte.nix @@ -0,0 +1 @@ +builtins.fromTOML ''k = "a\u0000b"''