From c7b6cce51e4a82a58bc71d8e1cbe8f7301cbf236 Mon Sep 17 00:00:00 2001 From: Anand Biju Date: Sun, 18 Oct 2026 22:49:23 +0000 Subject: [PATCH] Metadata API: Reject non-canonical bin names in SuccinctRoles SuccinctRoles.is_delegated_role() parsed the bin name suffix with int(suffix, 16). int() also accepts a leading sign, surrounding whitespace, underscores between digits and upper case digits, so names like "bin-+f", "bin- f", "bin-0F" or "bin-1_0" were reported as delegated even though get_roles() and get_role_for_target() never produce them. Only accept a suffix that matches the zero-padded lower case hex form used when the bin names are generated. Signed-off-by: Anand Biju --- tests/test_api.py | 11 +++++++++++ tuf/api/_payload.py | 5 +++++ 2 files changed, 16 insertions(+) diff --git a/tests/test_api.py b/tests/test_api.py index e156d6b332..294ee3926f 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1120,6 +1120,12 @@ def test_is_delegated_role_in_succinct_roles(self) -> None: "bin-0t", "bin-20", "bin-100", + # int(x, 16) accepts these, but they are not bin names + "bin-+f", + "bin- f", + "bin-f ", + "bin-0F", + "bin-1F", ] for role_name in false_role_name_examples: msg = f"Error for {role_name}" @@ -1131,6 +1137,11 @@ def test_is_delegated_role_in_succinct_roles(self) -> None: msg = f"Error for {role_name}" self.assertTrue(succinct_roles.is_delegated_role(role_name), msg) + # int(x, 16) ignores underscores between digits + succinct_roles = SuccinctRoles([], 1, 12, "bin") + self.assertFalse(succinct_roles.is_delegated_role("bin-1_0")) + self.assertTrue(succinct_roles.is_delegated_role("bin-010")) + def test_get_roles_in_succinct_roles(self) -> None: succinct_roles = SuccinctRoles([], 1, 16, "bin") # bin names are in hex format and 4 hex digits are enough to represent diff --git a/tuf/api/_payload.py b/tuf/api/_payload.py index 7b63cf5e01..582f5627ab 100644 --- a/tuf/api/_payload.py +++ b/tuf/api/_payload.py @@ -1385,6 +1385,11 @@ def is_delegated_role(self, role_name: str) -> bool: except ValueError: return False + # int() also accepts a sign, surrounding whitespace, underscores and + # upper case digits: only accept the exact name get_roles() produces + if suffix != f"{num:0{self.suffix_len}x}": + return False + return 0 <= num < self.number_of_bins