Package: release.debian.org
Severity: normal
Tags: trixie
X-Debbugs-Cc: [email protected]
Control: affects -1 + src:blazar
User: [email protected]
Usertags: pu

Hi,

[ Reason ]
I would like to upload a fix for CVE-2026-93852 and CVE-2026-93854
through Trixie p-u.

[ Impact ]
As per upstream announce:
https://security.openstack.org/ossa/OSSA-2026-040.html

* In OpenStack Blazar before 17.0.1, the V2 lease listing
operation (GET /v2/leases) returns leases for every project without
enforcing project scoping or an administrator-only policy. Any
authenticated user with access to the Blazar REST API can enumerate leases
belonging to other tenants, exposing lease IDs, reservation IDs, resource
IDs, and reservation metadata. The exposed lease IDs also enable the
object-level authorization bypass tracked in the companion request,
allowing an attacker to then modify or delete the enumerated leases.

* In OpenStack Blazar before 17.0.1, the V2 lease API does
not enforce object-level authorization on its update and delete operations
(PUT /v2/leases/{lease_id} and DELETE /v2/leases/{lease_id}). The policy
authorize() wrapper attempts to load the target lease to build the
authorization target from its owner, but it looks up the lease under the
keyword "lease_id" whereas the controller methods name the parameter "id"
(and the wsme_pecan.wsexpose wrapper delivers it positionally). The lookup
returns None, and thus authorization falls back to the requesting user's
own project_id/user_id instead of the target lease owner. Any authenticated
user who knows a lease ID can therefore modify or delete leases belonging
to other users and projects, bypassing the intended ownership check.


[ Tests ]
The included patch also modifes tests, which are used for this
part of the API, so it's covered.

There's also upstream functional testing.

[ Risks ]
Ok, because of upstream tests and unit tests.

[ Checklist ]
  [x] *all* changes are documented in the d/changelog
  [x] I reviewed all changes and I approve them
  [x] attach debdiff against the package in (old)stable
  [x] the issue is verified as fixed in unstable

[ Changes ]
Upstream patch.

Package is uploaded, and just need the Stable release team approval.

Cheers,

Thomas Goirand (zigo)
diff -Nru blazar-15.0.0/debian/changelog blazar-15.0.0/debian/changelog
--- blazar-15.0.0/debian/changelog      2025-05-12 12:36:04.000000000 +0200
+++ blazar-15.0.0/debian/changelog      2026-10-13 13:22:28.000000000 +0200
@@ -1,3 +1,31 @@
+blazar (15.0.0-2+deb13u1) trixie; urgency=medium
+
+  * CVE-2026-93852: In OpenStack Blazar before 17.0.1, the V2 lease listing
+    operation (GET /v2/leases) returns leases for every project without
+    enforcing project scoping or an administrator-only policy. Any
+    authenticated user with access to the Blazar REST API can enumerate leases
+    belonging to other tenants, exposing lease IDs, reservation IDs, resource
+    IDs, and reservation metadata. The exposed lease IDs also enable the
+    object-level authorization bypass tracked in the companion request,
+    allowing an attacker to then modify or delete the enumerated leases.
+    .
+    CVE-2026-93854: In OpenStack Blazar before 17.0.1, the V2 lease API does
+    not enforce object-level authorization on its update and delete operations
+    (PUT /v2/leases/{lease_id} and DELETE /v2/leases/{lease_id}). The policy
+    authorize() wrapper attempts to load the target lease to build the
+    authorization target from its owner, but it looks up the lease under the
+    keyword "lease_id" whereas the controller methods name the parameter "id"
+    (and the wsme_pecan.wsexpose wrapper delivers it positionally). The lookup
+    returns None, and thus authorization falls back to the requesting user's
+    own project_id/user_id instead of the target lease owner. Any authenticated
+    user who knows a lease ID can therefore modify or delete leases belonging
+    to other users and projects, bypassing the intended ownership check.
+    .
+    Applied upstream patch (Closes: #1148408).
+    - CVE-2026-93852_CVE-2026-93854_Fix_v2_API_object-level_authorizat....patch
+
+ -- Thomas Goirand <[email protected]>  Tue, 13 Oct 2026 13:22:28 +0200
+
 blazar (15.0.0-2) unstable; urgency=medium
 
   * Add fix-lease-date-not-enough-in-the-future.patch (Closes: #1104885).
diff -Nru 
blazar-15.0.0/debian/patches/CVE-2026-93852_CVE-2026-93854_Fix_v2_API_object-level_authorization_bypass.patch
 
blazar-15.0.0/debian/patches/CVE-2026-93852_CVE-2026-93854_Fix_v2_API_object-level_authorization_bypass.patch
--- 
blazar-15.0.0/debian/patches/CVE-2026-93852_CVE-2026-93854_Fix_v2_API_object-level_authorization_bypass.patch
       1970-01-01 01:00:00.000000000 +0100
+++ 
blazar-15.0.0/debian/patches/CVE-2026-93852_CVE-2026-93854_Fix_v2_API_object-level_authorization_bypass.patch
       2026-10-13 13:22:28.000000000 +0200
@@ -0,0 +1,202 @@
+Author: Matt Crees <[email protected]>
+Date: Thu, 17 Sep 2026 14:12:11 +0100
+Description: [PATCH] Fix v2 API object-level authorization bypass
+ Fixes two security flaws with the v2 API: any user could update/delete a
+ lease from any project via the v2 API, provided that they had the lease ID.
+ Additionally, any user could use the v2 API to list all leases, including
+ those in another project.
+ .
+ Operators that cannot yet upgrade should consider instead disabling the v2
+ API with the following config in ``blazar.conf``:
+ .
+ ```
+ [api]
+ api_v2_controllers =
+ ```
+Bug: https://launchpad.net/bugs/2162719
+Bug-Debian: https://bugs.debian.org/1148408
+Change-Id: I7b33491e96440d419e3e5a514f1a7a46b82547f5
+Signed-off-by: Matt Crees <[email protected]>
+Origin: upstream, https://review.opendev.org/c/openstack/blazar/+/1006053
+Last-Update: 2026-10-13
+
+diff --git a/blazar/api/v2/controllers/extensions/lease.py 
b/blazar/api/v2/controllers/extensions/lease.py
+index 6af8b89..d63c789 100644
+--- a/blazar/api/v2/controllers/extensions/lease.py
++++ b/blazar/api/v2/controllers/extensions/lease.py
+@@ -86,22 +86,28 @@
+ 
+     @policy.authorize('leases', 'get')
+     @wsme_pecan.wsexpose(Lease, types.UuidType())
+-    def get_one(self, id):
++    def get_one(self, lease_id):
+         """Returns the lease having this specific uuid
+ 
+-        :param id: ID of lease
++        :param lease_id: ID of lease
+         """
+-        lease = pecan.request.rpcapi.get_lease(id)
++        lease = pecan.request.rpcapi.get_lease(lease_id)
+         if lease is None:
+-            raise exceptions.NotFound(object={'lease_id': id})
++            raise exceptions.NotFound(object={'lease_id': lease_id})
+         return Lease.convert(lease)
+ 
+     @policy.authorize('leases', 'get')
+     @wsme_pecan.wsexpose([Lease], q=[])
+     def get_all(self):
+-        """Returns all leases."""
++        """Returns all leases for the current project."""
++        ctx = pecan.request.context
++        if policy.enforce(ctx, 'admin', {}, do_raise=False):
++            project_id = None
++        else:
++            project_id = ctx.project_id
+         return [Lease.convert(lease)
+-                for lease in pecan.request.rpcapi.list_leases()]
++                for lease in pecan.request.rpcapi.list_leases(
++                    project_id=project_id)]
+ 
+     @policy.authorize('leases', 'post')
+     @wsme_pecan.wsexpose(Lease, body=Lease, status_code=201)
+@@ -122,10 +128,10 @@
+ 
+     @policy.authorize('leases', 'put')
+     @wsme_pecan.wsexpose(Lease, types.UuidType(), body=Lease)
+-    def put(self, id, sublease):
++    def put(self, lease_id, sublease):
+         """Update an existing lease.
+ 
+-        :param id: UUID of a lease.
++        :param lease_id: UUID of a lease.
+         :param lease: a subset of a Lease containing values to update.
+         """
+         sublease_dct = sublease.as_dict()
+@@ -149,21 +155,21 @@
+         if before_end_date:
+             sublease_dct['before_end_date'] = before_end_date
+ 
+-        lease = pecan.request.rpcapi.update_lease(id, sublease_dct)
++        lease = pecan.request.rpcapi.update_lease(lease_id, sublease_dct)
+ 
+         if lease is None:
+-            raise exceptions.NotFound(object={'lease_id': id})
++            raise exceptions.NotFound(object={'lease_id': lease_id})
+         return Lease.convert(lease)
+ 
+     @policy.authorize('leases', 'delete')
+     @wsme_pecan.wsexpose(None, types.UuidType(), status_code=204)
+-    def delete(self, id):
++    def delete(self, lease_id):
+         """Delete an existing lease.
+ 
+-        :param id: UUID of a lease.
++        :param lease_id: UUID of a lease.
+         """
+         try:
+-            pecan.request.rpcapi.delete_lease(id)
++            pecan.request.rpcapi.delete_lease(lease_id)
+         except TypeError:
+             # The lease was not existing when asking to delete it
+-            raise exceptions.NotFound(object={'lease_id': id})
++            raise exceptions.NotFound(object={'lease_id': lease_id})
+diff --git a/blazar/policy.py b/blazar/policy.py
+index 7d37f3f..642ebd6 100644
+--- a/blazar/policy.py
++++ b/blazar/policy.py
+@@ -16,12 +16,14 @@
+ """Policy Engine For Blazar."""
+ 
+ import functools
++import inspect
+ 
+ from oslo_config import cfg
+ from oslo_log import log as logging
+ from oslo_policy import policy
+ 
+ from blazar import context
++from blazar.db import api as db_api
+ from blazar import exceptions
+ from blazar import policies
+ 
+@@ -101,8 +103,35 @@ def authorize(extension, action=None, api='blazar', 
ctx=None,
+         @functools.wraps(func)
+         def wrapped(self, *args, **kwargs):
+             cur_ctx = ctx or context.current()
+-            tgt = target or {'project_id': cur_ctx.project_id,
+-                             'user_id': cur_ctx.user_id}
++            tgt = target
++            if tgt is None:
++                obj = None
++                # NOTE(mattcrees): The V2 API wraps the requests with
++                # wsme_pecan.wsexpose, which causes kwargs to arrive as 
regular
++                # args instead. Work around this by inspecting the function 
and
++                # extracting out the lease_id kwarg.
++                # TODO(mattcrees): Revert to kwargs.get("lease_id") once the 
V2
++                # API is removed.
++                bound = inspect.signature(func).bind_partial(self, *args,
++                                                             **kwargs)
++                lease_id = kwargs.get('lease_id')
++                if lease_id is None:
++                    lease_id = bound.arguments.get('lease_id')
++                if lease_id is None and 'kwargs' in bound.arguments:
++                    lease_id = bound.arguments['kwargs'].get('lease_id')
++                if lease_id:
++                    obj = db_api.lease_get(lease_id)
++                if obj:
++                    tgt = {
++                        'project_id': obj.get("project_id"),
++                        'user_id': obj.get("user_id"),
++                    }
++                else:
++                    tgt = {
++                        'project_id': cur_ctx.project_id,
++                        'user_id': cur_ctx.user_id,
++                    }
++
+             if action is None:
+                 act = '%s:%s' % (api, extension)
+             else:
+diff --git a/blazar/tests/api/__init__.py b/blazar/tests/api/__init__.py
+index 6f0f9bc..d30b497 100644
+--- a/blazar/tests/api/__init__.py
++++ b/blazar/tests/api/__init__.py
+@@ -21,6 +21,7 @@
+ 
+ from blazar.api import context as api_context
+ from blazar import context
++from blazar.db import api as db_api
+ from blazar.manager.leases import rpcapi as leases_api
+ from blazar.manager.oshosts import rpcapi as hosts_rpcapi
+ from blazar import tests
+@@ -71,6 +72,8 @@
+         self.rpcapi = leases_api.ManagerRPCAPI
+         self.hosts_rpcapi = hosts_rpcapi.ManagerRPCAPI
+ 
++        self.patch(db_api, 'lease_get').return_value = None
++
+         # self.patch(rpcapi.ManagerRPCAPI, 'list_leases').return_value = []
+ 
+         def reset_pecan():
+diff --git 
a/releasenotes/notes/fix-v2-api-object-level-authorization-bypass-6fac65c3caa8a731.yaml
 
b/releasenotes/notes/fix-v2-api-object-level-authorization-bypass-6fac65c3caa8a731.yaml
+new file mode 100644
+index 0000000..bbe0a10
+--- /dev/null
++++ 
b/releasenotes/notes/fix-v2-api-object-level-authorization-bypass-6fac65c3caa8a731.yaml
+@@ -0,0 +1,16 @@
++---
++security:
++  - |
++    Fixed two security flaws with the v2 API: any user could update/delete a
++    lease from any project via the v2 API, provided that they had the lease 
ID.
++    Additionally, any user could use the v2 API to list all leases, including
++    those in another project.
++    `LP#2162719 <https://bugs.launchpad.net/blazar/+bug/2162719>`__
++
++    Operators that cannot yet upgrade should consider instead disabling the v2
++    API with the following config in ``blazar.conf``:
++
++    .. code-block:: ini
++
++        [api]
++        api_v2_controllers =
diff -Nru blazar-15.0.0/debian/patches/series 
blazar-15.0.0/debian/patches/series
--- blazar-15.0.0/debian/patches/series 2025-05-12 12:36:04.000000000 +0200
+++ blazar-15.0.0/debian/patches/series 2026-10-13 13:22:28.000000000 +0200
@@ -1,2 +1,3 @@
 install-all-files.patch
 fix-lease-date-not-enough-in-the-future.patch
+CVE-2026-93852_CVE-2026-93854_Fix_v2_API_object-level_authorization_bypass.patch

Reply via email to