1
0
mirror of https://github.com/systemd/systemd.git synced 2025-02-08 09:57:41 +03:00

Merge pull request #30962 from poettering/varlink-json-sensitive-rework

varlink/json: rework "sensitive" logic
This commit is contained in:
Luca Boccassi 2024-01-16 21:23:54 +00:00 committed by GitHub
commit db75b2aba7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 125 additions and 69 deletions

View File

@ -996,8 +996,6 @@ static int vl_method_encrypt(Varlink *link, JsonVariant *parameters, VarlinkMeth
assert(link);
json_variant_sensitive(parameters);
r = varlink_dispatch(link, parameters, dispatch_table, &p);
if (r != 0)
return r;
@ -1079,9 +1077,6 @@ static int vl_method_decrypt(Varlink *link, JsonVariant *parameters, VarlinkMeth
assert(link);
/* Let's also mark the (theoretically encrypted) input as sensitive, in case the NULL encryption scheme was used. */
json_variant_sensitive(parameters);
r = varlink_dispatch(link, parameters, dispatch_table, &p);
if (r != 0)
return r;
@ -1144,7 +1139,7 @@ static int run(int argc, char *argv[]) {
/* Invocation as Varlink service */
r = varlink_server_new(&varlink_server, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA);
r = varlink_server_new(&varlink_server, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA|VARLINK_SERVER_INPUT_SENSITIVE);
if (r < 0)
return log_error_errno(r, "Failed to allocate Varlink server: %m");

View File

@ -998,7 +998,7 @@ static int manager_bind_varlink(Manager *m) {
assert(m);
assert(!m->varlink_server);
r = varlink_server_new(&m->varlink_server, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA);
r = varlink_server_new(&m->varlink_server, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA|VARLINK_SERVER_INPUT_SENSITIVE);
if (r < 0)
return log_error_errno(r, "Failed to allocate varlink server object: %m");

View File

@ -89,6 +89,9 @@ struct JsonVariant {
/* Erase from memory when freeing */
bool sensitive:1;
/* True if we know that any referenced json object is marked sensitive */
bool recursive_sensitive:1;
/* If this is an object the fields are strictly ordered by name */
bool sorted:1;
@ -1453,6 +1456,33 @@ bool json_variant_is_sensitive(JsonVariant *v) {
return v->sensitive;
}
bool json_variant_is_sensitive_recursive(JsonVariant *v) {
if (!v)
return false;
if (json_variant_is_sensitive(v))
return true;
if (!json_variant_is_regular(v))
return false;
if (v->recursive_sensitive) /* Already checked this before */
return true;
if (!IN_SET(v->type, JSON_VARIANT_ARRAY, JSON_VARIANT_OBJECT))
return false;
if (v->is_reference) {
if (!json_variant_is_sensitive_recursive(v->reference))
return false;
return (v->recursive_sensitive = true);
}
for (size_t i = 0; i < json_variant_elements(v); i++)
if (json_variant_is_sensitive_recursive(json_variant_by_index(v, i)))
return (v->recursive_sensitive = true);
/* Note: we only cache the result here in case true, since we allow all elements down the tree to
* have their sensitive flag toggled later on (but never off) */
return false;
}
static void json_variant_propagate_sensitive(JsonVariant *from, JsonVariant *to) {
if (json_variant_is_sensitive(from))
json_variant_sensitive(to);
@ -1582,6 +1612,15 @@ static int json_format(FILE *f, JsonVariant *v, JsonFormatFlags flags, const cha
assert(f);
assert(v);
if (FLAGS_SET(flags, JSON_FORMAT_CENSOR_SENSITIVE) && json_variant_is_sensitive(v)) {
if (flags & JSON_FORMAT_COLOR)
fputs(ansi_red(), f);
fputs("\"<sensitive data>\"", f);
if (flags & JSON_FORMAT_COLOR)
fputs(ANSI_NORMAL, f);
return 0;
}
switch (json_variant_type(v)) {
case JSON_VARIANT_REAL: {
@ -1773,25 +1812,6 @@ static int json_format(FILE *f, JsonVariant *v, JsonFormatFlags flags, const cha
return 0;
}
static bool json_variant_is_sensitive_recursive(JsonVariant *v) {
if (!v)
return false;
if (json_variant_is_sensitive(v))
return true;
if (!json_variant_is_regular(v))
return false;
if (!IN_SET(v->type, JSON_VARIANT_ARRAY, JSON_VARIANT_OBJECT))
return false;
if (v->is_reference)
return json_variant_is_sensitive_recursive(v->reference);
for (size_t i = 0; i < json_variant_elements(v); i++)
if (json_variant_is_sensitive_recursive(json_variant_by_index(v, i)))
return true;
return false;
}
int json_variant_format(JsonVariant *v, JsonFormatFlags flags, char **ret) {
_cleanup_(memstream_done) MemStream m = {};
size_t sz;
@ -1807,10 +1827,6 @@ int json_variant_format(JsonVariant *v, JsonFormatFlags flags, char **ret) {
if (flags & JSON_FORMAT_OFF)
return -ENOEXEC;
if ((flags & JSON_FORMAT_REFUSE_SENSITIVE))
if (json_variant_is_sensitive_recursive(v))
return -EPERM;
f = memstream_init(&m);
if (!f)
return -ENOMEM;

View File

@ -155,6 +155,7 @@ bool json_variant_equal(JsonVariant *a, JsonVariant *b);
void json_variant_sensitive(JsonVariant *v);
bool json_variant_is_sensitive(JsonVariant *v);
bool json_variant_is_sensitive_recursive(JsonVariant *v);
struct json_variant_foreach_state {
JsonVariant *variant;
@ -196,7 +197,7 @@ typedef enum JsonFormatFlags {
JSON_FORMAT_FLUSH = 1 << 8, /* call fflush() after dumping JSON */
JSON_FORMAT_EMPTY_ARRAY = 1 << 9, /* output "[]" for empty input */
JSON_FORMAT_OFF = 1 << 10, /* make json_variant_format() fail with -ENOEXEC */
JSON_FORMAT_REFUSE_SENSITIVE = 1 << 11, /* return EPERM if any node in the tree is marked as senstitive */
JSON_FORMAT_CENSOR_SENSITIVE = 1 << 11, /* Replace all sensitive elements with the string "<sensitive data>" */
} JsonFormatFlags;
int json_variant_format(JsonVariant *v, JsonFormatFlags flags, char **ret);

View File

@ -184,6 +184,7 @@ struct Varlink {
bool allow_fd_passing_output:1;
bool output_buffer_sensitive:1; /* whether to erase the output buffer after writing it to the socket */
bool input_sensitive:1; /* Whether incoming messages might be sensitive */
int af; /* address family if socket; AF_UNSPEC if not socket; negative if not known */
@ -703,7 +704,7 @@ static void varlink_clear(Varlink *v) {
varlink_clear_current(v);
v->input_buffer = mfree(v->input_buffer);
v->input_buffer = v->input_sensitive ? erase_and_free(v->input_buffer) : mfree(v->input_buffer);
v->output_buffer = v->output_buffer_sensitive ? erase_and_free(v->output_buffer) : mfree(v->output_buffer);
v->input_control_buffer = mfree(v->input_control_buffer);
@ -1022,7 +1023,8 @@ static int varlink_read(Varlink *v) {
}
static int varlink_parse_message(Varlink *v) {
const char *e, *begin;
const char *e;
char *begin;
size_t sz;
int r;
@ -1047,6 +1049,8 @@ static int varlink_parse_message(Varlink *v) {
sz = e - begin + 1;
r = json_parse(begin, 0, &v->current, NULL, NULL);
if (v->input_sensitive)
explicit_bzero_safe(begin, sz);
if (r < 0) {
/* If we encounter a parse failure flush all data. We cannot possibly recover from this,
* hence drop all buffered data now. */
@ -1054,6 +1058,24 @@ static int varlink_parse_message(Varlink *v) {
return varlink_log_errno(v, r, "Failed to parse JSON: %m");
}
if (v->input_sensitive) {
/* Mark the parameters subfield as sensitive right-away, if that's requested */
JsonVariant *parameters = json_variant_by_key(v->current, "parameters");
if (parameters)
json_variant_sensitive(parameters);
}
if (DEBUG_LOGGING) {
_cleanup_(erase_and_freep) char *censored_text = NULL;
/* Suppress sensitive fields in the debug output */
r = json_variant_format(v->current, /* flags= */ JSON_FORMAT_CENSOR_SENSITIVE, &censored_text);
if (r < 0)
return r;
varlink_log(v, "Received message: %s", censored_text);
}
v->input_buffer_size -= sz;
if (v->input_buffer_size == 0)
@ -1849,56 +1871,60 @@ Varlink* varlink_flush_close_unref(Varlink *v) {
static int varlink_format_json(Varlink *v, JsonVariant *m) {
_cleanup_(erase_and_freep) char *text = NULL;
bool sensitive = false;
int r;
int sz, r;
assert(v);
assert(m);
r = json_variant_format(m, JSON_FORMAT_REFUSE_SENSITIVE, &text);
if (r == -EPERM) {
sensitive = true;
r = json_variant_format(m, /* flags= */ 0, &text);
}
if (r < 0)
return r;
assert(text[r] == '\0');
sz = json_variant_format(m, /* flags= */ 0, &text);
if (sz < 0)
return sz;
assert(text[sz] == '\0');
if (v->output_buffer_size + r + 1 > VARLINK_BUFFER_MAX)
if (v->output_buffer_size + sz + 1 > VARLINK_BUFFER_MAX)
return -ENOBUFS;
varlink_log(v, "Sending message: %s", sensitive ? "<sensitive data>" : text);
if (DEBUG_LOGGING) {
_cleanup_(erase_and_freep) char *censored_text = NULL;
/* Suppress sensitive fields in the debug output */
r = json_variant_format(m, /* flags= */ JSON_FORMAT_CENSOR_SENSITIVE, &censored_text);
if (r < 0)
return r;
varlink_log(v, "Sending message: %s", censored_text);
}
if (v->output_buffer_size == 0) {
free_and_replace(v->output_buffer, text);
v->output_buffer_size = r + 1;
v->output_buffer_size = sz + 1;
v->output_buffer_index = 0;
} else if (v->output_buffer_index == 0) {
if (!GREEDY_REALLOC(v->output_buffer, v->output_buffer_size + r + 1))
if (!GREEDY_REALLOC(v->output_buffer, v->output_buffer_size + sz + 1))
return -ENOMEM;
memcpy(v->output_buffer + v->output_buffer_size, text, r + 1);
v->output_buffer_size += r + 1;
memcpy(v->output_buffer + v->output_buffer_size, text, sz + 1);
v->output_buffer_size += sz + 1;
} else {
char *n;
const size_t new_size = v->output_buffer_size + r + 1;
const size_t new_size = v->output_buffer_size + sz + 1;
n = new(char, new_size);
if (!n)
return -ENOMEM;
memcpy(mempcpy(n, v->output_buffer + v->output_buffer_index, v->output_buffer_size), text, r + 1);
memcpy(mempcpy(n, v->output_buffer + v->output_buffer_index, v->output_buffer_size), text, sz + 1);
free_and_replace(v->output_buffer, n);
v->output_buffer_size = new_size;
v->output_buffer_index = 0;
}
if (sensitive)
if (json_variant_is_sensitive_recursive(m))
v->output_buffer_sensitive = true; /* Propagate sensitive flag */
else
text = mfree(text); /* No point in the erase_and_free() destructor declared above */
@ -3097,6 +3123,13 @@ int varlink_set_allow_fd_passing_output(Varlink *v, bool b) {
return 0;
}
int varlink_set_input_sensitive(Varlink *v) {
assert_return(v, -EINVAL);
v->input_sensitive = true;
return 0;
}
int varlink_server_new(VarlinkServer **ret, VarlinkServerFlags flags) {
_cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL;
int r;
@ -3325,6 +3358,9 @@ static int connect_callback(sd_event_source *source, int fd, uint32_t revents, v
TAKE_FD(cfd);
if (FLAGS_SET(ss->server->flags, VARLINK_SERVER_INPUT_SENSITIVE))
varlink_set_input_sensitive(v);
if (ss->server->connect_callback) {
r = ss->server->connect_callback(ss->server, v, ss->server->userdata);
if (r < 0) {

View File

@ -47,7 +47,8 @@ typedef enum VarlinkServerFlags {
VARLINK_SERVER_MYSELF_ONLY = 1 << 1, /* Only accessible by our own UID */
VARLINK_SERVER_ACCOUNT_UID = 1 << 2, /* Do per user accounting */
VARLINK_SERVER_INHERIT_USERDATA = 1 << 3, /* Initialize Varlink connection userdata from VarlinkServer userdata */
_VARLINK_SERVER_FLAGS_ALL = (1 << 4) - 1,
VARLINK_SERVER_INPUT_SENSITIVE = 1 << 4, /* Automatically mark al connection input as sensitive */
_VARLINK_SERVER_FLAGS_ALL = (1 << 5) - 1,
} VarlinkServerFlags;
typedef int (*VarlinkMethod)(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata);
@ -154,6 +155,9 @@ VarlinkServer* varlink_get_server(Varlink *v);
int varlink_set_description(Varlink *v, const char *d);
/* Automatically mark the parameters part of incoming messages as security sensitive */
int varlink_set_input_sensitive(Varlink *v);
/* Create a varlink server */
int varlink_server_new(VarlinkServer **ret, VarlinkServerFlags flags);
VarlinkServer *varlink_server_ref(VarlinkServer *s);

View File

@ -107,9 +107,9 @@ static void test_variant_one(const char *data, Test test) {
assert_se(json_variant_equal(v, w));
s = mfree(s);
r = json_variant_format(w, JSON_FORMAT_REFUSE_SENSITIVE, &s);
assert_se(r == -EPERM);
assert_se(!s);
r = json_variant_format(w, JSON_FORMAT_CENSOR_SENSITIVE, &s);
assert_se(s);
assert_se(streq_ptr(s, "\"<sensitive data>\""));
s = mfree(s);
r = json_variant_format(w, JSON_FORMAT_PRETTY, &s);
@ -886,10 +886,11 @@ TEST(json_sensitive) {
json_variant_sensitive(a);
assert_se(json_variant_format(a, JSON_FORMAT_REFUSE_SENSITIVE, &s) == -EPERM);
assert_se(!s);
assert_se(json_variant_format(a, JSON_FORMAT_CENSOR_SENSITIVE, &s) >= 0);
assert_se(streq_ptr(s, "\"<sensitive data>\""));
s = mfree(s);
r = json_variant_format(b, JSON_FORMAT_REFUSE_SENSITIVE, &s);
r = json_variant_format(b, JSON_FORMAT_CENSOR_SENSITIVE, &s);
assert_se(r >= 0);
assert_se(s);
assert_se((size_t) r == strlen(s));
@ -901,7 +902,7 @@ TEST(json_sensitive) {
JSON_BUILD_PAIR("e", JSON_BUILD_EMPTY_OBJECT))) >= 0);
json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL);
r = json_variant_format(v, JSON_FORMAT_REFUSE_SENSITIVE, &s);
r = json_variant_format(v, JSON_FORMAT_CENSOR_SENSITIVE, &s);
assert_se(r >= 0);
assert_se(s);
assert_se((size_t) r == strlen(s));
@ -915,7 +916,7 @@ TEST(json_sensitive) {
JSON_BUILD_PAIR("e", JSON_BUILD_EMPTY_OBJECT))) >= 0);
json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL);
r = json_variant_format(v, JSON_FORMAT_REFUSE_SENSITIVE, &s);
r = json_variant_format(v, JSON_FORMAT_CENSOR_SENSITIVE, &s);
assert_se(r >= 0);
assert_se(s);
assert_se((size_t) r == strlen(s));
@ -930,8 +931,9 @@ TEST(json_sensitive) {
JSON_BUILD_PAIR("e", JSON_BUILD_EMPTY_OBJECT))) >= 0);
json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL);
assert_se(json_variant_format(v, JSON_FORMAT_REFUSE_SENSITIVE, &s) == -EPERM);
assert_se(!s);
assert_se(json_variant_format(v, JSON_FORMAT_CENSOR_SENSITIVE, &s) >= 0);
assert_se(streq_ptr(s, "{\"b\":[\"foo\",\"bar\",\"baz\",\"qux\"],\"a\":\"<sensitive data>\",\"c\":-9223372036854775808,\"d\":\"-9223372036854775808\",\"e\":{}}"));
s = mfree(s);
v = json_variant_unref(v);
assert_se(json_build(&v, JSON_BUILD_OBJECT(
@ -942,8 +944,9 @@ TEST(json_sensitive) {
JSON_BUILD_PAIR("e", JSON_BUILD_EMPTY_OBJECT))) >= 0);
json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL);
assert_se(json_variant_format(v, JSON_FORMAT_REFUSE_SENSITIVE, &s) == -EPERM);
assert_se(!s);
assert_se(json_variant_format(v, JSON_FORMAT_CENSOR_SENSITIVE, &s) >= 0);
assert_se(streq_ptr(s, "{\"b\":[\"foo\",\"bar\",\"baz\",\"qux\"],\"c\":-9223372036854775808,\"a\":\"<sensitive data>\",\"d\":\"-9223372036854775808\",\"e\":{}}"));
s = mfree(s);
v = json_variant_unref(v);
assert_se(json_build(&v, JSON_BUILD_OBJECT(
@ -954,8 +957,9 @@ TEST(json_sensitive) {
JSON_BUILD_PAIR("e", JSON_BUILD_EMPTY_OBJECT))) >= 0);
json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL);
assert_se(json_variant_format(v, JSON_FORMAT_REFUSE_SENSITIVE, &s) == -EPERM);
assert_se(!s);
assert_se(json_variant_format(v, JSON_FORMAT_CENSOR_SENSITIVE, &s) >= 0);
assert_se(streq_ptr(s, "{\"b\":[\"foo\",\"bar\",\"baz\",\"qux\"],\"c\":-9223372036854775808,\"d\":\"-9223372036854775808\",\"a\":\"<sensitive data>\",\"e\":{}}"));
s = mfree(s);
v = json_variant_unref(v);
assert_se(json_build(&v, JSON_BUILD_OBJECT(
@ -966,8 +970,8 @@ TEST(json_sensitive) {
JSON_BUILD_PAIR_VARIANT("a", a))) >= 0);
json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL);
assert_se(json_variant_format(v, JSON_FORMAT_REFUSE_SENSITIVE, &s) == -EPERM);
assert_se(!s);
assert_se(json_variant_format(v, JSON_FORMAT_CENSOR_SENSITIVE, &s) >= 0);
assert_se(streq_ptr(s, "{\"b\":[\"foo\",\"bar\",\"baz\",\"qux\"],\"c\":-9223372036854775808,\"d\":\"-9223372036854775808\",\"e\":{},\"a\":\"<sensitive data>\"}"));
}
TEST(json_iovec) {