From 03de302a31323739b454a5746795c0f4150b87e8 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 26 Jan 2020 21:51:12 +0900 Subject: [PATCH 1/3] util: add parse_uid_range() helper function --- src/basic/user-util.c | 23 +++++++++++++++++++++++ src/basic/user-util.h | 1 + 2 files changed, 24 insertions(+) diff --git a/src/basic/user-util.c b/src/basic/user-util.c index 6d22fe31c3..a491f5505e 100644 --- a/src/basic/user-util.c +++ b/src/basic/user-util.c @@ -62,6 +62,29 @@ int parse_uid(const char *s, uid_t *ret) { return 0; } +int parse_uid_range(const char *s, uid_t *ret_lower, uid_t *ret_upper) { + uint32_t u, l; + int r; + + assert(s); + assert(ret_lower); + assert(ret_upper); + + r = parse_range(s, &l, &u); + if (r < 0) + return r; + + if (l > u) + return -EINVAL; + + if (!uid_is_valid(l) || !uid_is_valid(u)) + return -ENXIO; + + *ret_lower = l; + *ret_upper = u; + return 0; +} + char* getlogname_malloc(void) { uid_t uid; struct stat st; diff --git a/src/basic/user-util.h b/src/basic/user-util.h index 714e83082c..6796ac42c1 100644 --- a/src/basic/user-util.h +++ b/src/basic/user-util.h @@ -19,6 +19,7 @@ static inline bool gid_is_valid(gid_t gid) { } int parse_uid(const char *s, uid_t* ret_uid); +int parse_uid_range(const char *s, uid_t *ret_lower, uid_t *ret_upper); static inline int parse_gid(const char *s, gid_t *ret_gid) { return parse_uid(s, (uid_t*) ret_gid); From ea471a4695723979021d37ab31c45210107ea689 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 26 Jan 2020 22:22:38 +0900 Subject: [PATCH 2/3] network: support UID based routing policy Closes #14666. --- man/systemd.network.xml | 7 ++ src/network/networkd-manager.c | 6 ++ src/network/networkd-network-gperf.gperf | 1 + src/network/networkd-routing-policy-rule.c | 85 ++++++++++++++++++- src/network/networkd-routing-policy-rule.h | 2 + .../fuzz-network-parser/directives.network | 1 + 6 files changed, 100 insertions(+), 2 deletions(-) diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 6ff4fb2440..ed5abeff6c 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -1084,6 +1084,13 @@ ipv4. + + User= + + Takes a username, a user ID, or a range of user IDs separated by a dash. Defaults to + unset. + + diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index 90f734a03c..2528fb5267 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -1138,6 +1138,12 @@ int manager_rtnl_process_rule(sd_netlink *rtnl, sd_netlink_message *message, voi return 0; } + r = sd_netlink_message_read(message, FRA_UID_RANGE, sizeof(tmp->uid_range), &tmp->uid_range); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: could not get FRA_UID_RANGE attribute, ignoring: %m"); + return 0; + } + (void) routing_policy_rule_get(m, tmp, &rule); if (DEBUG_LOGGING) { diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 2a7c223129..699691dc87 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -130,6 +130,7 @@ RoutingPolicyRule.SourcePort, config_parse_routing_policy_rule_port_ra RoutingPolicyRule.DestinationPort, config_parse_routing_policy_rule_port_range, 0, 0 RoutingPolicyRule.InvertRule, config_parse_routing_policy_rule_invert, 0, 0 RoutingPolicyRule.Family, config_parse_routing_policy_rule_family, 0, 0 +RoutingPolicyRule.User, config_parse_routing_policy_rule_uid_range, 0, 0 Route.Gateway, config_parse_gateway, 0, 0 Route.Destination, config_parse_destination, 0, 0 Route.Source, config_parse_destination, 0, 0 diff --git a/src/network/networkd-routing-policy-rule.c b/src/network/networkd-routing-policy-rule.c index e7d09c4ad9..52f25209d0 100644 --- a/src/network/networkd-routing-policy-rule.c +++ b/src/network/networkd-routing-policy-rule.c @@ -7,6 +7,7 @@ #include "alloc-util.h" #include "conf-parser.h" #include "fileio.h" +#include "format-util.h" #include "ip-protocol-list.h" #include "networkd-routing-policy-rule.h" #include "netlink-util.h" @@ -16,6 +17,7 @@ #include "socket-util.h" #include "string-util.h" #include "strv.h" +#include "user-util.h" int routing_policy_rule_new(RoutingPolicyRule **ret) { RoutingPolicyRule *rule; @@ -26,6 +28,8 @@ int routing_policy_rule_new(RoutingPolicyRule **ret) { *rule = (RoutingPolicyRule) { .table = RT_TABLE_MAIN, + .uid_range.start = UID_INVALID, + .uid_range.end = UID_INVALID, }; *ret = rule; @@ -93,6 +97,7 @@ static int routing_policy_rule_copy(RoutingPolicyRule *dest, RoutingPolicyRule * dest->protocol = src->protocol; dest->sport = src->sport; dest->dport = src->dport; + dest->uid_range = src->uid_range; return 0; } @@ -122,6 +127,7 @@ static void routing_policy_rule_hash_func(const RoutingPolicyRule *rule, struct siphash24_compress(&rule->protocol, sizeof(rule->protocol), state); siphash24_compress(&rule->sport, sizeof(rule->sport), state); siphash24_compress(&rule->dport, sizeof(rule->dport), state); + siphash24_compress(&rule->uid_range, sizeof(rule->uid_range), state); if (rule->iif) siphash24_compress(rule->iif, strlen(rule->iif), state); @@ -198,6 +204,10 @@ static int routing_policy_rule_compare_func(const RoutingPolicyRule *a, const Ro if (r != 0) return r; + r = memcmp(&a->uid_range, &b->uid_range, sizeof(a->uid_range)); + if (r != 0) + return r; + r = strcmp_ptr(a->iif, b->iif); if (r != 0) return r; @@ -554,6 +564,12 @@ int routing_policy_rule_configure(RoutingPolicyRule *rule, Link *link, link_netl return log_link_error_errno(link, r, "Could not append FRA_DPORT_RANGE attribute: %m"); } + if (rule->uid_range.start != UID_INVALID && rule->uid_range.end != UID_INVALID) { + r = sd_netlink_message_append_data(m, FRA_UID_RANGE, &rule->uid_range, sizeof(rule->uid_range)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append FRA_UID_RANGE attribute: %m"); + } + if (rule->invert_rule) { r = sd_rtnl_message_routing_policy_rule_set_flags(m, FIB_RULE_INVERT); if (r < 0) @@ -1056,6 +1072,51 @@ int config_parse_routing_policy_rule_family( return 0; } +int config_parse_routing_policy_rule_uid_range( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(routing_policy_rule_free_or_set_invalidp) RoutingPolicyRule *n = NULL; + Network *network = userdata; + uid_t start, end; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = routing_policy_rule_new_static(network, filename, section_line, &n); + if (r < 0) + return r; + + r = get_user_creds(&rvalue, &start, NULL, NULL, NULL, 0); + if (r >= 0) + end = start; + else { + r = parse_uid_range(rvalue, &start, &end); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Invalid uid or uid range '%s', ignoring: %m", rvalue); + return 0; + } + } + + n->uid_range.start = start; + n->uid_range.end = end; + n = NULL; + return 0; +} + static int routing_policy_rule_read_full_file(const char *state_file, char **ret) { _cleanup_free_ char *s = NULL; size_t size; @@ -1170,6 +1231,14 @@ int routing_policy_serialize_rules(Set *rules, FILE *f) { space = true; } + if (rule->uid_range.start != UID_INVALID && rule->uid_range.end != UID_INVALID) { + assert_cc(sizeof(uid_t) == sizeof(uint32_t)); + fprintf(f, "%suidrange="UID_FMT"-"UID_FMT, + space ? " " : "", + rule->uid_range.start, rule->uid_range.end); + space = true; + } + fprintf(f, "%stable=%"PRIu32 "\n", space ? " " : "", rule->table); @@ -1294,7 +1363,7 @@ int routing_policy_load_rules(const char *state_file, Set **rules) { r = parse_ip_port_range(b, &low, &high); if (r < 0) { - log_error_errno(r, "Invalid routing policy rule source port range, ignoring assignment:'%s'", b); + log_error_errno(r, "Invalid routing policy rule source port range, ignoring assignment: '%s'", b); continue; } @@ -1305,12 +1374,24 @@ int routing_policy_load_rules(const char *state_file, Set **rules) { r = parse_ip_port_range(b, &low, &high); if (r < 0) { - log_error_errno(r, "Invalid routing policy rule destination port range, ignoring assignment:'%s'", b); + log_error_errno(r, "Invalid routing policy rule destination port range, ignoring assignment: '%s'", b); continue; } rule->dport.start = low; rule->dport.end = high; + + } else if (streq(a, "uidrange")) { + uid_t lower, upper; + + r = parse_uid_range(b, &lower, &upper); + if (r < 0) { + log_error_errno(r, "Invalid routing policy rule uid range, ignoring assignment: '%s'", b); + continue; + } + + rule->uid_range.start = lower; + rule->uid_range.end = upper; } } diff --git a/src/network/networkd-routing-policy-rule.h b/src/network/networkd-routing-policy-rule.h index 6b8e310227..2afd8f75a6 100644 --- a/src/network/networkd-routing-policy-rule.h +++ b/src/network/networkd-routing-policy-rule.h @@ -49,6 +49,7 @@ struct RoutingPolicyRule { struct fib_rule_port_range sport; struct fib_rule_port_range dport; + struct fib_rule_uid_range uid_range; LIST_FIELDS(RoutingPolicyRule, rules); }; @@ -79,3 +80,4 @@ CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_port_range); CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_ip_protocol); CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_invert); CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_family); +CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_uid_range); diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network index 0e3adac5ce..8a953b75d9 100644 --- a/test/fuzz/fuzz-network-parser/directives.network +++ b/test/fuzz/fuzz-network-parser/directives.network @@ -225,6 +225,7 @@ DestinationPort= IPProtocol= InvertRule= Family= +User= [IPv6PrefixDelegation] RouterPreference= DNSLifetimeSec= From 6be8e78e32eca7c5aa7bd6f0860b33466e98ac7a Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 26 Jan 2020 22:39:32 +0900 Subject: [PATCH 3/3] test-network: add test for UID based routing policy --- .../conf/25-fibrule-uidrange.network | 9 ++++++ test/test-network/systemd-networkd-tests.py | 31 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 test/test-network/conf/25-fibrule-uidrange.network diff --git a/test/test-network/conf/25-fibrule-uidrange.network b/test/test-network/conf/25-fibrule-uidrange.network new file mode 100644 index 0000000000..f42dfee325 --- /dev/null +++ b/test/test-network/conf/25-fibrule-uidrange.network @@ -0,0 +1,9 @@ +[Match] +Name=test1 + +[RoutingPolicyRule] +TypeOfService=0x08 +Table=7 +From= 192.168.100.18 +Priority=111 +User=100-200 diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index eb4eebebae..01292d7c3b 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -100,6 +100,23 @@ def expectedFailureIfRoutingPolicyIPProtoIsNotAvailable(): return f +def expectedFailureIfRoutingPolicyUIDRangeIsNotAvailable(): + def f(func): + support = False + rc = call('ip rule add from 192.168.100.19 table 7 uidrange 200-300', stderr=subprocess.DEVNULL) + if rc == 0: + ret = run('ip rule list from 192.168.100.19 table 7', stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + if ret.returncode == 0 and 'uidrange 200-300' in ret.stdout.rstrip(): + support = True + call('ip rule del from 192.168.100.19 table 7 uidrange 200-300') + + if support: + return func + else: + return unittest.expectedFailure(func) + + return f + def expectedFailureIfLinkFileFieldIsNotSet(): def f(func): support = False @@ -1572,6 +1589,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): '25-bond-active-backup-slave.netdev', '25-fibrule-invert.network', '25-fibrule-port-range.network', + '25-fibrule-uidrange.network', '25-gre-tunnel-remote-any.netdev', '25-ip6gre-tunnel-remote-any.netdev', '25-ipv6-address-label-section.network', @@ -1776,6 +1794,19 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): self.assertRegex(output, 'tcp') self.assertRegex(output, 'lookup 7') + @expectedFailureIfRoutingPolicyUIDRangeIsNotAvailable() + def test_routing_policy_rule_uidrange(self): + copy_unit_to_networkd_unit_path('25-fibrule-uidrange.network', '11-dummy.netdev') + start_networkd() + self.wait_online(['test1:degraded']) + + output = check_output('ip rule') + print(output) + self.assertRegex(output, '111') + self.assertRegex(output, 'from 192.168.100.18') + self.assertRegex(output, 'lookup 7') + self.assertRegex(output, 'uidrange 100-200') + def test_route_static(self): copy_unit_to_networkd_unit_path('25-route-static.network', '12-dummy.netdev') start_networkd()