Hi, On Thu, Oct 13, 2022 at 09:13:18PM +0200, Moritz Mühlenhoff wrote: > Source: lava > X-Debbugs-CC: t...@security.debian.org > Severity: grave > Tags: security > > Hi, > > The following vulnerability was published for lava. > > CVE-2022-42902[0]: > | In Linaro Automated Validation Architecture (LAVA) before 2022.10, > | there is dynamic code execution in lava_server/lavatable.py. Due to > | improper input sanitization, an anonymous user can force the lava- > | server-gunicorn service to execute user-provided code on the server. > > https://git.lavasoftware.org/lava/lava/-/merge_requests/1834 > https://git.lavasoftware.org/lava/lava/-/commit/e66b74cd6c175ff8826b8f3431740963be228b52?merge_request_iid=1834 > > If you fix the vulnerability please also make sure to include the > CVE (Common Vulnerabilities & Exposures) id in your changelog entry. > > For further information see: > > [0] https://security-tracker.debian.org/tracker/CVE-2022-42902 > https://www.cve.org/CVERecord?id=CVE-2022-42902 > > Please adjust the affected versions in the BTS as needed.
I have uploaded a fix version to unstable (latest upstream), and I would like to upload the attached debdiff to -security. That package builds cleanly and passes its autopkgtest on bullseye. Let me know. I'm also attaching the 2 patches included directly, since they are easier to read than the diff-in-diff. The main patch for the security issue is exactly the one that was applied upstream.
From e66b74cd6c175ff8826b8f3431740963be228b52 Mon Sep 17 00:00:00 2001 From: Igor Ponomarev <igor.ponoma...@collabora.com> Date: Mon, 26 Sep 2022 18:51:47 +0300 Subject: [PATCH] Replace dynamic code execution in lava_server/lavatable.py `exec` was used to create query parameters for the tables searches. `exec` is extremely dangerous and can lead to remote code execution. It is also very slow. The new implementation is equivalent in function but uses dictionaries and unpacking to create arguments. --- lava_server/lavatable.py | 76 ++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 50 deletions(-) --- a/lava_server/lavatable.py +++ b/lava_server/lavatable.py @@ -41,8 +41,6 @@ class LavaView(tables.SingleTableView): """ bespoke time-based field handling """ - local_namespace = locals() - local_namespace["q"] = query time_queries = {} if hasattr(self.table_class.Meta, "times"): # filter the possible list by the request @@ -50,26 +48,20 @@ class LavaView(tables.SingleTableView): # check if the request includes the current time filter & get the value match = self.request.GET.get(key) if match and match != "": - self.terms[key] = "%s within %s %s" % ( - key, - match, - value, - ) # the label for this query in the search list + self.terms[key] = f"{key} within {match} {value}" + # the label for this query in the search list time_queries[key] = value for key, value in time_queries.items(): match = escape(self.request.GET.get(key)) # escape converts None into u'None' if not match or match == "" or match == "None": continue - args = "q = q.__and__(Q({0}__gte=timezone.now()-timedelta({1}={2})))".format( - key, value, match + + query &= Q( + **{f"{key}__gte": timezone.now() - timedelta(**{value: int(match)})} ) - try: - exec(args, globals(), local_namespace) # sets the value of q - except SyntaxError: - # should log the exception somewhere... - continue # just skip this term - results in a query matching All. - return local_namespace["q"] + + return query def get_table_data(self, prefix=None): """ @@ -145,60 +137,44 @@ class LavaView(tables.SingleTableView): if not self.request: return data - local_namespace = locals() - local_namespace["q"] = Q() + q = Q() self.terms = {} # discrete searches for key, val in distinct.items(): if key in self.table_class.Meta.searches: - args = 'q = q.__and__(Q({0}__contains="{1}"))'.format(key, val) - try: - exec(args, globals(), local_namespace) # sets the value of q - except SyntaxError: - # should log exception somewhere... - continue # just skip this term - results in a query matching All. + q &= Q(**{f"{key}__contains": val}) + if ( hasattr(self.table_class.Meta, "queries") and key in self.table_class.Meta.queries.keys() ): - # note that this calls the function 'key' with the argument from the search - args = 'q = q.__and__(self.{0}("{1}"))'.format(key, val) - try: - exec(args, globals(), local_namespace) - except SyntaxError: - # should log exception somewhere... - continue + # note that this calls + # the function 'key' with the argument from the search + q &= getattr(self, key)(val) + # general OR searches if self.request.GET.get(table_search): self.terms["search"] = escape(self.request.GET.get(table_search)) if hasattr(self.table_class.Meta, "searches") and "search" in self.terms: for key, val in self.table_class.Meta.searches.items(): - # this is a little bit of magic - creates an OR clause in the query based - # on the iterable search hash passed in via the table_class + # this is a little bit of magic - creates an OR clause + # in the query based on the iterable search hash + # passed in via the table_class # e.g. self.searches = {'id', 'contains'} - # so every simple search column in the table is queried at the same time with OR - args = 'q = q.__or__(Q({0}__{1}=self.terms["search"]))'.format(key, val) - try: - exec(args, globals(), local_namespace) # sets the value of q - except SyntaxError: - # should log exception somewhere... - continue # just skip this term - results in a query matching All. + # so every simple search column in the table + # is queried at the same time with OR + q |= Q(**{f"{key}__{val}": self.terms["search"]}) + # call explicit handlers as simple text searches of relational fields. if hasattr(self.table_class.Meta, "queries"): for key in self.table_class.Meta.queries: - # note that this calls the function 'key' with the argument from the search - args = 'q = q.__or__(self.{0}("{1}"))'.format( - key, self.terms["search"].encode("utf-8") - ) - try: - exec(args, globals(), local_namespace) - except SyntaxError: - # should log exception somewhere... - continue + # note that this calls the function 'key' + # with the argument from the search + q |= getattr(self, key)(self.terms["search"]) + # now add "class specials" - from an iterable hash # datetime uses (start_time__lte=timezone.now()-timedelta(days=3) - data = data.filter(self._time_filter(local_namespace["q"])) - return data + return data.filter(self._time_filter(q)) class LavaTable(tables.Table):
From e1f934df702a1db386e301fbe2d51ab4154ee21c Mon Sep 17 00:00:00 2001 From: Antonio Terceiro <antonio.terce...@linaro.org> Date: Tue, 18 Oct 2022 17:57:46 -0300 Subject: [PATCH] share/requires.py: fix building for debian -backports and -security suites --- share/requires.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/share/requires.py b/share/requires.py index 2599028d8..00aadc3a4 100755 --- a/share/requires.py +++ b/share/requires.py @@ -123,6 +123,8 @@ def main(): help="Distribution package names for unittest support - requires --names", ) args = parser.parse_args() + args.suite = args.suite.replace("-backports", "") + args.suite = args.suite.replace("-security", "") if args.unittests and not args.names: raise RuntimeError("--unittests option requires --names") try: -- 2.35.1
diff -Nru lava-2020.12/debian/changelog lava-2020.12/debian/changelog --- lava-2020.12/debian/changelog 2021-05-24 09:05:18.000000000 -0300 +++ lava-2020.12/debian/changelog 2022-10-18 17:24:50.000000000 -0300 @@ -1,3 +1,10 @@ +lava (2020.12-5+deb111u1) bullseye-security; urgency=high + + * Fix remote code execution [CVE-2022-42902] (Closes: #1021737) + * Add patch to fix building the package for -security + + -- Antonio Terceiro <terce...@debian.org> Tue, 18 Oct 2022 17:24:50 -0300 + lava (2020.12-5) unstable; urgency=medium * Fix PyYAML usage after backwards-incompatible security update diff -Nru lava-2020.12/debian/patches/0003-Replace-dynamic-code-execution-in-lava_server-lavata.patch lava-2020.12/debian/patches/0003-Replace-dynamic-code-execution-in-lava_server-lavata.patch --- lava-2020.12/debian/patches/0003-Replace-dynamic-code-execution-in-lava_server-lavata.patch 1969-12-31 21:00:00.000000000 -0300 +++ lava-2020.12/debian/patches/0003-Replace-dynamic-code-execution-in-lava_server-lavata.patch 2022-10-18 17:24:33.000000000 -0300 @@ -0,0 +1,139 @@ +From e66b74cd6c175ff8826b8f3431740963be228b52 Mon Sep 17 00:00:00 2001 +From: Igor Ponomarev <igor.ponoma...@collabora.com> +Date: Mon, 26 Sep 2022 18:51:47 +0300 +Subject: [PATCH] Replace dynamic code execution in lava_server/lavatable.py + +`exec` was used to create query parameters for the tables searches. +`exec` is extremely dangerous and can lead to remote code execution. +It is also very slow. +The new implementation is equivalent in function but uses +dictionaries and unpacking to create arguments. +--- + lava_server/lavatable.py | 76 ++++++++++++++-------------------------- + 1 file changed, 26 insertions(+), 50 deletions(-) + +--- a/lava_server/lavatable.py ++++ b/lava_server/lavatable.py +@@ -41,8 +41,6 @@ class LavaView(tables.SingleTableView): + """ + bespoke time-based field handling + """ +- local_namespace = locals() +- local_namespace["q"] = query + time_queries = {} + if hasattr(self.table_class.Meta, "times"): + # filter the possible list by the request +@@ -50,26 +48,20 @@ class LavaView(tables.SingleTableView): + # check if the request includes the current time filter & get the value + match = self.request.GET.get(key) + if match and match != "": +- self.terms[key] = "%s within %s %s" % ( +- key, +- match, +- value, +- ) # the label for this query in the search list ++ self.terms[key] = f"{key} within {match} {value}" ++ # the label for this query in the search list + time_queries[key] = value + for key, value in time_queries.items(): + match = escape(self.request.GET.get(key)) + # escape converts None into u'None' + if not match or match == "" or match == "None": + continue +- args = "q = q.__and__(Q({0}__gte=timezone.now()-timedelta({1}={2})))".format( +- key, value, match ++ ++ query &= Q( ++ **{f"{key}__gte": timezone.now() - timedelta(**{value: int(match)})} + ) +- try: +- exec(args, globals(), local_namespace) # sets the value of q +- except SyntaxError: +- # should log the exception somewhere... +- continue # just skip this term - results in a query matching All. +- return local_namespace["q"] ++ ++ return query + + def get_table_data(self, prefix=None): + """ +@@ -145,60 +137,44 @@ class LavaView(tables.SingleTableView): + if not self.request: + return data + +- local_namespace = locals() +- local_namespace["q"] = Q() ++ q = Q() + self.terms = {} + # discrete searches + for key, val in distinct.items(): + if key in self.table_class.Meta.searches: +- args = 'q = q.__and__(Q({0}__contains="{1}"))'.format(key, val) +- try: +- exec(args, globals(), local_namespace) # sets the value of q +- except SyntaxError: +- # should log exception somewhere... +- continue # just skip this term - results in a query matching All. ++ q &= Q(**{f"{key}__contains": val}) ++ + if ( + hasattr(self.table_class.Meta, "queries") + and key in self.table_class.Meta.queries.keys() + ): +- # note that this calls the function 'key' with the argument from the search +- args = 'q = q.__and__(self.{0}("{1}"))'.format(key, val) +- try: +- exec(args, globals(), local_namespace) +- except SyntaxError: +- # should log exception somewhere... +- continue ++ # note that this calls ++ # the function 'key' with the argument from the search ++ q &= getattr(self, key)(val) ++ + # general OR searches + if self.request.GET.get(table_search): + self.terms["search"] = escape(self.request.GET.get(table_search)) + if hasattr(self.table_class.Meta, "searches") and "search" in self.terms: + for key, val in self.table_class.Meta.searches.items(): +- # this is a little bit of magic - creates an OR clause in the query based +- # on the iterable search hash passed in via the table_class ++ # this is a little bit of magic - creates an OR clause ++ # in the query based on the iterable search hash ++ # passed in via the table_class + # e.g. self.searches = {'id', 'contains'} +- # so every simple search column in the table is queried at the same time with OR +- args = 'q = q.__or__(Q({0}__{1}=self.terms["search"]))'.format(key, val) +- try: +- exec(args, globals(), local_namespace) # sets the value of q +- except SyntaxError: +- # should log exception somewhere... +- continue # just skip this term - results in a query matching All. ++ # so every simple search column in the table ++ # is queried at the same time with OR ++ q |= Q(**{f"{key}__{val}": self.terms["search"]}) ++ + # call explicit handlers as simple text searches of relational fields. + if hasattr(self.table_class.Meta, "queries"): + for key in self.table_class.Meta.queries: +- # note that this calls the function 'key' with the argument from the search +- args = 'q = q.__or__(self.{0}("{1}"))'.format( +- key, self.terms["search"].encode("utf-8") +- ) +- try: +- exec(args, globals(), local_namespace) +- except SyntaxError: +- # should log exception somewhere... +- continue ++ # note that this calls the function 'key' ++ # with the argument from the search ++ q |= getattr(self, key)(self.terms["search"]) ++ + # now add "class specials" - from an iterable hash + # datetime uses (start_time__lte=timezone.now()-timedelta(days=3) +- data = data.filter(self._time_filter(local_namespace["q"])) +- return data ++ return data.filter(self._time_filter(q)) + + + class LavaTable(tables.Table): diff -Nru lava-2020.12/debian/patches/0004-share-requires.py-fix-building-for-debian-backports-.patch lava-2020.12/debian/patches/0004-share-requires.py-fix-building-for-debian-backports-.patch --- lava-2020.12/debian/patches/0004-share-requires.py-fix-building-for-debian-backports-.patch 1969-12-31 21:00:00.000000000 -0300 +++ lava-2020.12/debian/patches/0004-share-requires.py-fix-building-for-debian-backports-.patch 2022-10-18 17:24:50.000000000 -0300 @@ -0,0 +1,26 @@ +From e1f934df702a1db386e301fbe2d51ab4154ee21c Mon Sep 17 00:00:00 2001 +From: Antonio Terceiro <antonio.terce...@linaro.org> +Date: Tue, 18 Oct 2022 17:57:46 -0300 +Subject: [PATCH] share/requires.py: fix building for debian -backports and + -security suites + +--- + share/requires.py | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/share/requires.py b/share/requires.py +index 2599028d8..00aadc3a4 100755 +--- a/share/requires.py ++++ b/share/requires.py +@@ -123,6 +123,8 @@ def main(): + help="Distribution package names for unittest support - requires --names", + ) + args = parser.parse_args() ++ args.suite = args.suite.replace("-backports", "") ++ args.suite = args.suite.replace("-security", "") + if args.unittests and not args.names: + raise RuntimeError("--unittests option requires --names") + try: +-- +2.35.1 + diff -Nru lava-2020.12/debian/patches/series lava-2020.12/debian/patches/series --- lava-2020.12/debian/patches/series 2021-05-24 09:05:18.000000000 -0300 +++ lava-2020.12/debian/patches/series 2022-10-18 17:24:50.000000000 -0300 @@ -1,2 +1,4 @@ 0001-lava_rest_app-fix-field-name-in-filters.patch 0002-lava_common.compat-add-support-for-PyYAML-5.4.patch +0003-Replace-dynamic-code-execution-in-lava_server-lavata.patch +0004-share-requires.py-fix-building-for-debian-backports-.patch
signature.asc
Description: PGP signature