resolve: cleanups for validating query flags and fix resolving service with record type filter (#36398)

Follow-up for 81ae2237c1.
Fixes
https://github.com/systemd/systemd/pull/36353#issuecomment-2659558382.
This commit is contained in:
Lennart Poettering
2025-02-20 17:31:19 +01:00
committed by GitHub
5 changed files with 155 additions and 116 deletions

View File

@@ -361,51 +361,6 @@ finish:
}
}
static int validate_and_mangle_flags(
const char *name,
uint64_t *flags,
uint64_t ok,
sd_bus_error *error) {
assert(flags);
/* Checks that the client supplied interface index and flags parameter actually are valid and make
* sense in our method call context. Specifically:
*
* 1. Checks that the interface index is either 0 (meaning *all* interfaces) or positive
*
* 2. Only the protocols flags and a bunch of NO_XYZ flags are set, at most. Plus additional flags
* specific to our method, passed in the "ok" parameter.
*
* 3. If zero protocol flags are specified it is automatically turned into *all* protocols. This way
* clients can simply pass 0 as flags and all will work as it should. They can also use this so
* that clients don't have to know all the protocols resolved implements, but can just specify 0
* to mean "all supported protocols".
*/
if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL|
SD_RESOLVED_NO_CNAME|
SD_RESOLVED_NO_VALIDATE|
SD_RESOLVED_NO_SYNTHESIZE|
SD_RESOLVED_NO_CACHE|
SD_RESOLVED_NO_ZONE|
SD_RESOLVED_NO_TRUST_ANCHOR|
SD_RESOLVED_NO_NETWORK|
SD_RESOLVED_NO_STALE|
SD_RESOLVED_RELAX_SINGLE_LABEL|
ok))
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags parameter");
if ((*flags & SD_RESOLVED_PROTOCOLS_ALL) == 0) /* If no protocol is enabled, enable all */
*flags |= SD_RESOLVED_PROTOCOLS_ALL;
/* Imply SD_RESOLVED_NO_SEARCH if permitted and name is dot suffixed. */
if (name && FLAGS_SET(ok, SD_RESOLVED_NO_SEARCH) && dns_name_dot_suffixed(name) > 0)
*flags |= SD_RESOLVED_NO_SEARCH;
return 0;
}
static int parse_as_address(sd_bus_message *m, int ifindex, const char *hostname, int family, uint64_t flags) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_free_ char *canonical = NULL;
@@ -519,9 +474,8 @@ static int bus_method_resolve_hostname(sd_bus_message *message, void *userdata,
if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family);
r = validate_and_mangle_flags(hostname, &flags, SD_RESOLVED_NO_SEARCH, error);
if (r < 0)
return r;
if (validate_and_mangle_query_flags(m, &flags, hostname, SD_RESOLVED_NO_SEARCH) < 0)
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags parameter");
r = parse_as_address(message, ifindex, hostname, family, flags);
if (r != 0)
@@ -674,9 +628,8 @@ static int bus_method_resolve_address(sd_bus_message *message, void *userdata, s
if (ifindex < 0)
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid interface index");
r = validate_and_mangle_flags(NULL, &flags, 0, error);
if (r < 0)
return r;
if (validate_and_mangle_query_flags(m, &flags, /* name = */ NULL, /* ok = */ 0) < 0)
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags parameter");
r = dns_question_new_reverse(&question, family, &a);
if (r < 0)
@@ -843,9 +796,8 @@ static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd
if (dns_type_is_obsolete(type))
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Specified DNS resource record type %" PRIu16 " is obsolete.", type);
r = validate_and_mangle_flags(name, &flags, 0, error);
if (r < 0)
return r;
if (validate_and_mangle_query_flags(m, &flags, name, SD_RESOLVED_NO_SEARCH) < 0)
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags parameter");
question = dns_question_new(1);
if (!question)
@@ -1378,9 +1330,12 @@ static int bus_method_resolve_service(sd_bus_message *message, void *userdata, s
if (name && !type)
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Service name cannot be specified without service type.");
r = validate_and_mangle_flags(name, &flags, SD_RESOLVED_NO_TXT|SD_RESOLVED_NO_ADDRESS, error);
if (r < 0)
return r;
if (validate_and_mangle_query_flags(m, &flags, name, SD_RESOLVED_NO_TXT|SD_RESOLVED_NO_ADDRESS) < 0)
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags parameter");
/* Refuse the method if SRV is filtered. */
if (set_contains(m->refuse_record_types, INT_TO_PTR(DNS_TYPE_SRV)))
return sd_bus_error_set(error, BUS_ERROR_DNS_REFUSED, "DNS query type refused.");
r = dns_question_new_service(&question_utf8, name, type, domain, !(flags & SD_RESOLVED_NO_TXT), false);
if (r < 0)

View File

@@ -1440,3 +1440,60 @@ bool dns_query_fully_authoritative(DnsQuery *q) {
* explicitly check previous redirects here.) */
return (q->answer_query_flags & SD_RESOLVED_FROM_MASK & ~(SD_RESOLVED_FROM_TRUST_ANCHOR | SD_RESOLVED_FROM_ZONE)) == 0;
}
int validate_and_mangle_query_flags(
Manager *manager,
uint64_t *flags,
const char *name,
uint64_t ok) {
assert(manager);
assert(flags);
/* Checks that the client supplied interface index and flags parameter actually are valid and make
* sense in our method call context. Specifically:
*
* 1. Checks that the interface index is either 0 (meaning *all* interfaces) or positive
*
* 2. Only the protocols flags and a bunch of NO_XYZ flags are set, at most. Plus additional flags
* specific to our method, passed in the "ok" parameter.
*
* 3. If zero protocol flags are specified it is automatically turned into *all* protocols. This way
* clients can simply pass 0 as flags and all will work as it should. They can also use this so
* that clients don't have to know all the protocols resolved implements, but can just specify 0
* to mean "all supported protocols".
*/
if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL|
SD_RESOLVED_NO_CNAME|
SD_RESOLVED_NO_VALIDATE|
SD_RESOLVED_NO_SYNTHESIZE|
SD_RESOLVED_NO_CACHE|
SD_RESOLVED_NO_ZONE|
SD_RESOLVED_NO_TRUST_ANCHOR|
SD_RESOLVED_NO_NETWORK|
SD_RESOLVED_NO_STALE|
SD_RESOLVED_RELAX_SINGLE_LABEL|
ok))
return -EINVAL;
if ((*flags & SD_RESOLVED_PROTOCOLS_ALL) == 0) /* If no protocol is enabled, enable all */
*flags |= SD_RESOLVED_PROTOCOLS_ALL;
/* Imply SD_RESOLVED_NO_SEARCH if permitted and name is dot suffixed. */
if (name && FLAGS_SET(ok, SD_RESOLVED_NO_SEARCH) && dns_name_dot_suffixed(name) > 0)
*flags |= SD_RESOLVED_NO_SEARCH;
/* If both A and AAAA are refused, set SD_RESOLVED_NO_ADDRESS flag if it is allowed. */
if (set_contains(manager->refuse_record_types, INT_TO_PTR(DNS_TYPE_A)) &&
set_contains(manager->refuse_record_types, INT_TO_PTR(DNS_TYPE_AAAA)) &&
FLAGS_SET(ok, SD_RESOLVED_NO_ADDRESS))
*flags |= SD_RESOLVED_NO_ADDRESS;
/* Similarly, if TXT is refused, set SD_RESOLVED_NO_TXT flag if it is allowed. */
if (set_contains(manager->refuse_record_types, INT_TO_PTR(DNS_TYPE_TXT)) &&
FLAGS_SET(ok, SD_RESOLVED_NO_TXT))
*flags |= SD_RESOLVED_NO_TXT;
return 0;
}

View File

@@ -158,6 +158,8 @@ bool dns_query_fully_authenticated(DnsQuery *q);
bool dns_query_fully_confidential(DnsQuery *q);
bool dns_query_fully_authoritative(DnsQuery *q);
int validate_and_mangle_query_flags(Manager *manager, uint64_t *flags, const char *name, uint64_t ok);
static inline uint64_t dns_query_reply_flags_make(DnsQuery *q) {
assert(q);

View File

@@ -166,52 +166,6 @@ static void vl_on_notification_disconnect(sd_varlink_server *s, sd_varlink *link
}
}
static bool validate_and_mangle_flags(
const char *name,
uint64_t *flags,
uint64_t ok) {
assert(flags);
/* This checks that the specified client-provided flags parameter actually makes sense, and mangles
* it slightly. Specifically:
*
* 1. We check that only the protocol flags and a bunch of NO_XYZ flags are on at most, plus the
* method-specific flags specified in 'ok'.
*
* 2. If no protocols are enabled we automatically convert that to "all protocols are enabled".
*
* The second rule means that clients can just pass 0 as flags for the common case, and all supported
* protocols are enabled. Moreover it's useful so that client's do not have to be aware of all
* protocols implemented in resolved, but can use 0 as protocols flags set as indicator for
* "everything".
*/
if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL|
SD_RESOLVED_NO_CNAME|
SD_RESOLVED_NO_VALIDATE|
SD_RESOLVED_NO_SYNTHESIZE|
SD_RESOLVED_NO_CACHE|
SD_RESOLVED_NO_ZONE|
SD_RESOLVED_NO_TRUST_ANCHOR|
SD_RESOLVED_NO_NETWORK|
SD_RESOLVED_NO_STALE|
SD_RESOLVED_RELAX_SINGLE_LABEL|
ok))
return false;
if ((*flags & SD_RESOLVED_PROTOCOLS_ALL) == 0) /* If no protocol is enabled, enable all */
*flags |= SD_RESOLVED_PROTOCOLS_ALL;
/* If the SD_RESOLVED_NO_SEARCH flag is acceptable, and the query name is dot-suffixed, turn off
* search domains. Note that DNS name normalization drops the dot suffix, hence we propagate this
* into the flags field as early as we can. */
if (name && FLAGS_SET(ok, SD_RESOLVED_NO_SEARCH) && dns_name_dot_suffixed(name) > 0)
*flags |= SD_RESOLVED_NO_SEARCH;
return true;
}
static int find_addr_records(
sd_json_variant **array,
DnsQuestion *question,
@@ -387,7 +341,7 @@ static int vl_method_resolve_hostname(sd_varlink *link, sd_json_variant *paramet
if (!IN_SET(p.family, AF_UNSPEC, AF_INET, AF_INET6))
return sd_varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("family"));
if (!validate_and_mangle_flags(p.name, &p.flags, SD_RESOLVED_NO_SEARCH))
if (validate_and_mangle_query_flags(m, &p.flags, p.name, SD_RESOLVED_NO_SEARCH) < 0)
return sd_varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("flags"));
r = parse_as_address(link, &p);
@@ -550,7 +504,7 @@ static int vl_method_resolve_address(sd_varlink *link, sd_json_variant *paramete
if (FAMILY_ADDRESS_SIZE(p.family) != p.address_size)
return sd_varlink_error(link, "io.systemd.Resolve.BadAddressSize", NULL);
if (!validate_and_mangle_flags(NULL, &p.flags, 0))
if (validate_and_mangle_query_flags(m, &p.flags, /* name = */ NULL, /* ok = */ 0) < 0)
return sd_varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("flags"));
r = dns_question_new_reverse(&question, p.family, &p.address);
@@ -1044,9 +998,13 @@ static int vl_method_resolve_service(sd_varlink* link, sd_json_variant* paramete
if (p.name && !p.type) /* Service name cannot be specified without service type. */
return sd_varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("type"));
if (!validate_and_mangle_flags(p.name, &p.flags, SD_RESOLVED_NO_TXT|SD_RESOLVED_NO_ADDRESS))
if (validate_and_mangle_query_flags(m, &p.flags, p.name, SD_RESOLVED_NO_TXT|SD_RESOLVED_NO_ADDRESS) < 0)
return sd_varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("flags"));
/* Refuse the method if SRV is filtered. */
if (set_contains(m->refuse_record_types, INT_TO_PTR(DNS_TYPE_SRV)))
return sd_varlink_error(link, "io.systemd.Resolve.QueryRefused", NULL);
r = dns_question_new_service(&question_utf8, p.name, p.type, p.domain, !(p.flags & SD_RESOLVED_NO_TXT), false);
if (r < 0)
return r;
@@ -1184,7 +1142,7 @@ static int vl_method_resolve_record(sd_varlink *link, sd_json_variant *parameter
if (dns_type_is_obsolete(p.type))
return sd_varlink_error(link, "io.systemd.Resolve.ResourceRecordTypeObsolete", NULL);
if (!validate_and_mangle_flags(p.name, &p.flags, SD_RESOLVED_NO_SEARCH))
if (validate_and_mangle_query_flags(m, &p.flags, p.name, SD_RESOLVED_NO_SEARCH) < 0)
return sd_varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("flags"));
_cleanup_(dns_question_unrefp) DnsQuestion *question = dns_question_new(1);

View File

@@ -551,6 +551,7 @@ testcase_08_resolved() {
# Check SRV support
run resolvectl service _mysvc._tcp signed.test
grep -qF "myservice.signed.test:1234" "$RUN_OUT"
grep -qF "This is TXT for myservice" "$RUN_OUT"
grep -qF "10.0.0.20" "$RUN_OUT"
grep -qF "fd00:dead:beef:cafe::17" "$RUN_OUT"
grep -qF "authenticated: yes" "$RUN_OUT"
@@ -850,7 +851,6 @@ testcase_11_nft() {
echo "[Resolve]"
echo "StaleRetentionSec=1d"
} >/run/systemd/resolved.conf.d/test.conf
ln -svf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
systemctl reload systemd-resolved.service
run dig stale1.unsigned.test -t A
@@ -943,6 +943,7 @@ testcase_12_resolvectl2() {
cleanup() {
rm -f /run/systemd/resolved.conf.d/reload.conf
systemctl reload systemd-resolved.service
resolvectl revert dns0
}
trap cleanup RETURN
@@ -1024,6 +1025,7 @@ testcase_13_varlink_subscribe_dns_configuration() {
echo "=========="
rm -f /run/systemd/resolved.conf.d/global-dns.conf
restart_resolved
resolvectl revert dns0
}
trap cleanup RETURN ERR
@@ -1096,10 +1098,6 @@ testcase_14_refuse_record_types() {
# shellcheck disable=SC2317
cleanup() {
rm -f /run/systemd/resolved.conf.d/refuserecords.conf
if [[ -e /etc/resolv.conf.bak ]]; then
rm -f /etc/resolv.conf
mv /etc/resolv.conf.bak /etc/resolv.conf
fi
restart_resolved
}
trap cleanup RETURN ERR
@@ -1109,11 +1107,8 @@ testcase_14_refuse_record_types() {
echo "[Resolve]"
echo "RefuseRecordTypes=AAAA SRV TXT"
} >/run/systemd/resolved.conf.d/refuserecords.conf
if [[ -e /etc/resolv.conf ]]; then
mv /etc/resolv.conf /etc/resolv.conf.bak
fi
ln -svf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
systemctl reload systemd-resolved.service
run dig localhost -t AAAA
grep -qF "status: REFUSED" "$RUN_OUT"
@@ -1141,6 +1136,10 @@ testcase_14_refuse_record_types() {
run resolvectl query localhost5 --type=A
grep -qF "127.128.0.5" "$RUN_OUT"
(! run resolvectl service _mysvc._tcp signed.test)
(! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveService '{"name":"","type":"_mysvc._tcp","domain":"signed.test"}')
# Filter only AAAA
{
echo "[Resolve]"
echo "RefuseRecordTypes=AAAA"
@@ -1164,6 +1163,74 @@ testcase_14_refuse_record_types() {
(! run resolvectl query localhost5 --type=AAAA)
grep -qF "DNS query type refused." "$RUN_OUT"
run resolvectl service _mysvc._tcp signed.test
grep -qF "myservice.signed.test:1234" "$RUN_OUT"
grep -qF "This is TXT for myservice" "$RUN_OUT"
grep -qF "10.0.0.20" "$RUN_OUT"
(! grep -qF "fd00:dead:beef:cafe::17" "$RUN_OUT")
grep -qF "authenticated: yes" "$RUN_OUT"
run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveService '{"name":"","type":"_mysvc._tcp","domain":"signed.test"}'
grep -qF '"services":[{"priority":10,"weight":5,"port":1234,"hostname":"myservice.signed.test","canonicalName":"myservice.signed.test"' "$RUN_OUT"
grep -qF '"addresses":[{"ifindex":' "$RUN_OUT"
grep -qF '"family":2,"address":[10,0,0,20]' "$RUN_OUT"
(! grep -qF '"family":10,"address":[253,0,222,173,190,239,202,254,0,0,0,0,0,0,0,23]' "$RUN_OUT")
grep -qF '"txt":["This is TXT for myservice"]' "$RUN_OUT"
grep -qF '"canonical":{"name":null,"type":"_mysvc._tcp","domain":"signed.test"}' "$RUN_OUT"
# Filter both A and AAAA
{
echo "[Resolve]"
echo "RefuseRecordTypes=A AAAA"
} >/run/systemd/resolved.conf.d/refuserecords.conf
systemctl reload systemd-resolved.service
run resolvectl service _mysvc._tcp signed.test
grep -qF "myservice.signed.test:1234" "$RUN_OUT"
grep -qF "This is TXT for myservice" "$RUN_OUT"
(! grep -qF "10.0.0.20" "$RUN_OUT")
(! grep -qF "fd00:dead:beef:cafe::17" "$RUN_OUT")
grep -qF "authenticated: yes" "$RUN_OUT"
run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveService '{"name":"","type":"_mysvc._tcp","domain":"signed.test"}'
grep -qF '"services":[{"priority":10,"weight":5,"port":1234,"hostname":"myservice.signed.test"}]' "$RUN_OUT"
(! grep -qF '"addresses":[{"ifindex":' "$RUN_OUT")
(! grep -qF '"family":2,"address":[10,0,0,20]' "$RUN_OUT")
(! grep -qF '"family":10,"address":[253,0,222,173,190,239,202,254,0,0,0,0,0,0,0,23]' "$RUN_OUT")
grep -qF '"txt":["This is TXT for myservice"]' "$RUN_OUT"
grep -qF '"canonical":{"name":null,"type":"_mysvc._tcp","domain":"signed.test"}' "$RUN_OUT"
# Filter AAAA and TXT
{
echo "[Resolve]"
echo "RefuseRecordTypes=AAAA TXT"
} >/run/systemd/resolved.conf.d/refuserecords.conf
systemctl reload systemd-resolved.service
run resolvectl service _mysvc._tcp signed.test
grep -qF "myservice.signed.test:1234" "$RUN_OUT"
grep -qF "10.0.0.20" "$RUN_OUT"
(! grep -qF "fd00:dead:beef:cafe::17" "$RUN_OUT")
grep -qF "authenticated: yes" "$RUN_OUT"
run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveService '{"name":"","type":"_mysvc._tcp","domain":"signed.test"}'
grep -qF '"services":[{"priority":10,"weight":5,"port":1234,"hostname":"myservice.signed.test","canonicalName":"myservice.signed.test"' "$RUN_OUT"
grep -qF '"addresses":[{"ifindex":' "$RUN_OUT"
grep -qF '"family":2,"address":[10,0,0,20]' "$RUN_OUT"
(! grep -qF '"family":10,"address":[253,0,222,173,190,239,202,254,0,0,0,0,0,0,0,23]' "$RUN_OUT")
(! grep -qF '"txt":["This is TXT for myservice"]' "$RUN_OUT")
grep -qF '"canonical":{"name":null,"type":"_mysvc._tcp","domain":"signed.test"}' "$RUN_OUT"
# Filter SRV
{
echo "[Resolve]"
echo "RefuseRecordTypes=SRV"
} >/run/systemd/resolved.conf.d/refuserecords.conf
systemctl reload systemd-resolved.service
(! run resolvectl service _mysvc._tcp signed.test)
(! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveService '{"name":"","type":"_mysvc._tcp","domain":"signed.test"}')
}
# PRE-SETUP