core: Try to prevent infinite recursive template instantiation

To prevent situations like in #17602 from happening, let's drop
direct recursive template dependencies. These will almost certainly
lead to infinite recursion so let's drop them immediately to avoid
instantiating potentially thousands of irrelevant units.

Example of a template that would lead to infinite recursion which
is caught by this check:

notify@.service:

```
[Unit]
Wants=notify@%n.service
```
This commit is contained in:
Daan De Meyer
2021-10-28 11:42:21 +01:00
parent 11e9347bc6
commit 88022148c4
7 changed files with 210 additions and 2 deletions
+67
View File
@@ -829,6 +829,71 @@ static void test_config_parse_memory_limit(void) {
}
static void test_contains_instance_specifier_superset(void) {
assert_se(contains_instance_specifier_superset("foobar@a%i"));
assert_se(contains_instance_specifier_superset("foobar@%ia"));
assert_se(contains_instance_specifier_superset("foobar@%n"));
assert_se(contains_instance_specifier_superset("foobar@%n.service"));
assert_se(contains_instance_specifier_superset("foobar@%N"));
assert_se(contains_instance_specifier_superset("foobar@%N.service"));
assert_se(contains_instance_specifier_superset("foobar@baz.%N.service"));
assert_se(contains_instance_specifier_superset("@%N.service"));
assert_se(contains_instance_specifier_superset("@%N"));
assert_se(contains_instance_specifier_superset("@%a%N"));
assert_se(!contains_instance_specifier_superset("foobar@%i.service"));
assert_se(!contains_instance_specifier_superset("foobar%ia.service"));
assert_se(!contains_instance_specifier_superset("foobar@%%n.service"));
assert_se(!contains_instance_specifier_superset("foobar@baz.service"));
assert_se(!contains_instance_specifier_superset("%N.service"));
assert_se(!contains_instance_specifier_superset("%N"));
assert_se(!contains_instance_specifier_superset("@%aN"));
assert_se(!contains_instance_specifier_superset("@%a%b"));
}
static void test_unit_is_recursive_template_dependency(void) {
_cleanup_(manager_freep) Manager *m = NULL;
Unit *u;
int r;
r = manager_new(UNIT_FILE_USER, MANAGER_TEST_RUN_MINIMAL, &m);
if (manager_errno_skip_test(r)) {
log_notice_errno(r, "Skipping test: manager_new: %m");
return;
}
assert_se(r >= 0);
assert_se(manager_startup(m, NULL, NULL, NULL) >= 0);
assert_se(u = unit_new(m, sizeof(Service)));
assert_se(unit_add_name(u, "foobar@1.service") == 0);
u->fragment_path = strdup("/foobar@.service");
assert_se(hashmap_put_strdup(&m->unit_id_map, "foobar@foobar@123.service", "/foobar@.service"));
assert_se(hashmap_put_strdup(&m->unit_id_map, "foobar@foobar@456.service", "/custom.service"));
/* Test that %n, %N and any extension of %i specifiers in the instance are detected as recursive. */
assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@123.service", "foobar@%N.service") == 1);
assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@123.service", "foobar@%n.service") == 1);
assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@123.service", "foobar@a%i.service") == 1);
assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@123.service", "foobar@%ia.service") == 1);
assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@123.service", "foobar@%x%n.service") == 1);
/* Test that %i on its own is not detected as recursive. */
assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@123.service", "foobar@%i.service") == 0);
/* Test that a specifier other than %i, %n and %N is not detected as recursive. */
assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@123.service", "foobar@%xn.service") == 0);
/* Test that an expanded specifier is not detected as recursive. */
assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@123.service", "foobar@foobar@123.service") == 0);
/* Test that a dependency with a custom fragment path is not detected as recursive. */
assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@456.service", "foobar@%n.service") == 0);
/* Test that a dependency without a fragment path is not detected as recursive. */
assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@789.service", "foobar@%n.service") == 0);
/* Test that a dependency with a different prefix is not detected as recursive. */
assert_se(unit_is_likely_recursive_template_dependency(u, "quux@foobar@123.service", "quux@%n.service") == 0);
/* Test that a dependency of a different type is not detected as recursive. */
assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@123.mount", "foobar@%n.mount") == 0);
}
int main(int argc, char *argv[]) {
_cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL;
int r;
@@ -850,6 +915,8 @@ int main(int argc, char *argv[]) {
TEST_REQ_RUNNING_SYSTEMD(test_install_printf());
test_unit_dump_config_items();
test_config_parse_memory_limit();
test_contains_instance_specifier_superset();
test_unit_is_recursive_template_dependency();
return r;
}
+17
View File
@@ -870,6 +870,22 @@ static void test_unit_name_from_dbus_path(void) {
test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/wpa_5fsupplicant_2eservice", 0, "wpa_supplicant.service");
}
static void test_unit_name_prefix_equal(void) {
log_info("/* %s */", __func__);
assert_se(unit_name_prefix_equal("a.service", "a.service"));
assert_se(unit_name_prefix_equal("a.service", "a.mount"));
assert_se(unit_name_prefix_equal("a@b.service", "a.service"));
assert_se(unit_name_prefix_equal("a@b.service", "a@c.service"));
assert_se(!unit_name_prefix_equal("a.service", "b.service"));
assert_se(!unit_name_prefix_equal("a.service", "b.mount"));
assert_se(!unit_name_prefix_equal("a@a.service", "b.service"));
assert_se(!unit_name_prefix_equal("a@a.service", "b@a.service"));
assert_se(!unit_name_prefix_equal("a", "b"));
assert_se(!unit_name_prefix_equal("a", "a"));
}
int main(int argc, char* argv[]) {
_cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL;
int r, rc = 0;
@@ -902,6 +918,7 @@ int main(int argc, char* argv[]) {
test_unit_name_path_unescape();
test_unit_name_to_prefix();
test_unit_name_from_dbus_path();
test_unit_name_prefix_equal();
return rc;
}