See how canonical compares to other vendors in security performance
Last updated 5 June 2026
Last updated 5 June 2026
Last updated 5 June 2026
Last updated 5 June 2026
Last updated 5 June 2026
In Ubuntu, Subiquity version 24.04.4 could leak sensitive user credentials during crash reporting. Upon installation failure, if a user submitted a bug report to Launchpad, Subiquity could include certain user credentials, such as the user's plaintext Wi-Fi password, in the attached logs.
In Ubuntu, ubuntu-desktop-provision version 24.04.4 could leak sensitive user credentials during crash reporting. Upon installation failure, if a user submitted a bug report to Launchpad, ubuntu-desktop-provision could include the user's password hash in the attached logs.
Summary The GET /1.0/certificates endpoint (non-recursive mode) returns URLs containing fingerprints for all certificates in the trust store, bypassing the per-object canview authorization check that is correctly applied in the recursive path. Any authenticated identity — including restricted, non-admin users — can enumerate all certificate fingerprints, exposing the full set of trusted identities in the LXD deployment.
Affected Component - lxd/certificates.go — certificatesGet (lines 185–192) — Non-recursive code path returns unfiltered certificate list.
CWE - CWE-862: Missing Authorization
Description
Core vulnerability: missing permission filter in non-recursive listing path
The certificatesGet handler obtains a permission checker at line 143 and correctly applies it when building the recursive response (lines 163-176). However, the non-recursive code path at lines 185-192 creates a fresh loop over the unfiltered baseCerts slice, completely bypassing the authorization check:
go // lxd/certificates.go:139-193 func certificatesGet(d Daemon, r http.Request) response.Response { recursion := util.IsRecursionRequest(r) s := d.State()
userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), auth.EntitlementCanView, entity.TypeCertificate) // ...
for , baseCert := range baseCerts { if !userHasPermission(entity.CertificateURL(baseCert.Fingerprint)) { continue // Correctly filters unauthorized certs }
if recursion { // ... builds filtered certResponses ... } // NOTE: when !recursion, nothing is recorded — the filter result is discarded }
if !recursion { body := []string{} for , baseCert := range baseCerts { // <-- iterates UNFILTERED baseCerts certificateURL := api.NewURL().Path(version.APIVersion, "certificates", baseCert.Fingerprint).String() body = append(body, certificateURL) } return response.SyncResponse(true, body) // Returns ALL certificate fingerprints }
return response.SyncResponse(true, certResponses) // Recursive path is correctly filtered }
Inconsistency with other list endpoints confirms the bug
Five other list endpoints in the same codebase correctly filter results in both recursive and non-recursive paths:
| Endpoint | File | Filters non-recursive? | |----------|------|----------------------| | Instances | lxd/instancesget.go — instancesGet | Yes — filters before either path | | Images | lxd/images.go — doImagesGet | Yes — checks hasPermission for both paths | | Networks | lxd/networks.go — networksGet | Yes — filters outside recursion check | | Profiles | lxd/profiles.go — profilesGet | Yes — separate filter in non-recursive path | | Certificates | lxd/certificates.go — certificatesGet | No — unfiltered |
The certificates endpoint is the sole outlier, confirming this is an oversight rather than a design choice.
Access handler provides no defense
The endpoint uses allowAuthenticated as its AccessHandler (certificates.go:45), which only checks requestor.IsTrusted():
go // lxd/daemon.go:255-267 // allowAuthenticated is an AccessHandler which allows only authenticated requests. // This should be used in conjunction with further access control within the handler // (e.g. to filter resources the user is able to view/edit). func allowAuthenticated( Daemon, r http.Request) response.Response { requestor, err := request.GetRequestor(r.Context()) // ... if requestor.IsTrusted() { return response.EmptySyncResponse } return response.Forbidden(nil) }
The comment explicitly states that allowAuthenticated should be "used in conjunction with further access control within the handler" — which the non-recursive path fails to do.
Execution chain
1. Restricted authenticated user sends GET /1.0/certificates (no recursion parameter) 2. allowAuthenticated access handler passes because user is trusted (daemon.go:263) 3. certificatesGet creates permission checker for EntitlementCanView on TypeCertificate (line 143) 4. Loop at lines 163-176 filters baseCerts by permission — but only populates certResponses for recursive mode 5. Since !recursion, control reaches lines 185-192 6. New loop iterates ALL baseCerts (unfiltered) and builds URL list with fingerprints 7. Full list of certificate fingerprints returned to restricted user
Proof of Concept
bash Preconditions: restricted (non-admin) trusted client certificate HOST=target.example PORT=8443
1) Non-recursive list: returns ALL certificate fingerprints (UNFILTERED) curl -sk --cert restricted.crt --key restricted.key \ "https://${HOST}:${PORT}/1.0/certificates" | jq '.metadata | length'
2) Recursive list: returns only authorized certificates (FILTERED) curl -sk --cert restricted.crt --key restricted.key \ "https://${HOST}:${PORT}/1.0/certificates?recursion=1" | jq '.metadata | length'
Expected: (1) returns MORE fingerprints than (2), proving the authorization bypass. The difference reveals fingerprints of certificates the restricted user should not see.
Impact
- Identity enumeration: A restricted user can discover the fingerprints of all trusted certificates, revealing the complete set of identities in the LXD trust store. - Reconnaissance for targeted attacks: Fingerprints identify specific certificates used for inter-cluster communication, admin access, and other privileged operations. - RBAC bypass: In deployments using fine-grained RBAC (OpenFGA or built-in TLS authorization), the non-recursive path completely bypasses the intended per-object visibility controls. - Information asymmetry: Restricted users gain knowledge of the full trust topology, which the administrator explicitly intended to hide via per-certificate canview entitlements.
Recommended Remediation
Option 1: Apply the permission filter to the non-recursive path (preferred)
Replace the unfiltered loop with one that checks userHasPermission, matching the pattern used in the recursive path and in all other list endpoints:
go // lxd/certificates.go — replace lines 185-192 if !recursion { body := []string{} for , baseCert := range baseCerts { if !userHasPermission(entity.CertificateURL(baseCert.Fingerprint)) { continue } certificateURL := api.NewURL().Path(version.APIVersion, "certificates", baseCert.Fingerprint).String() body = append(body, certificateURL) } return response.SyncResponse(true, body) }
Option 2: Build both response types in a single filtered loop
Restructure the function to build both the URL list and the recursive response in the same permission-checked loop, eliminating the possibility of divergent filtering:
go err = d.State().DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx db.ClusterTx) error { baseCerts, err = dbCluster.GetCertificates(ctx, tx.Tx()) if err != nil { return err }
certResponses = make([]api.Certificate, 0, len(baseCerts)) certURLs = make([]string, 0, len(baseCerts)) for , baseCert := range baseCerts { if !userHasPermission(entity.CertificateURL(baseCert.Fingerprint)) { continue }
certURLs = append(certURLs, api.NewURL().Path(version.APIVersion, "certificates", baseCert.Fingerprint).String())
if recursion { apiCert, err := baseCert.ToAPI(ctx, tx.Tx()) if err != nil { return err } certResponses = append(certResponses, apiCert) urlToCertificate[entity.CertificateURL(apiCert.Fingerprint)] = apiCert } } return nil })
Option 2 is structurally safer as it prevents the two paths from diverging in the future.
Credit This vulnerability was discovered and reported by bugbunny.ai.
It was discovered that processcrash() in data/apport in Canonical's Apport crash reporting tool may create crash files with incorrect group ownership, possibly exposing crash information beyond expected or intended groups.
gdbus setgid privilege escalation
Summary If a server.ca file is present in LXDDIR at LXD start up, LXD is in "PKI mode". In this mode, all clients must have certificates that have been signed by the CA.
The LXD configuration option core.trustcacertificates defaults to false. This means that although the client certificate has been signed by the CA, LXD will additionally add the certificate to the trust store and verify it via mTLS.
When a restricted certificate is added to the trust store in this mode, it's restrictions are not honoured, and the client has full access to LXD.
Details When authorization was refactored to allow for generalisation (at the time for TLS, RBAC, and OpenFGA, see https://github.com/canonical/lxd/pull/12313), PKI mode did not account for the core.trustcacertificates configuration option. When this option is enabled, all CA-signed client certificates are given full access to LXD. This cherry-pick from Incus was added to LXD to fix the issue.
The cherry-pick fixed the immediate issue and allowed full access to LXD for CA-signed client certificates when core.trustcacertificates is enabled, but did not consider the behaviour of LXD when core.trustcacertificates is disabled.
When core.trustcacertificates is false, restrictions that are applied to a certificate should be honoured. Instead, they are being ignored due to the presence of a server.ca file in LXDDIR.
PoC Install/initialize LXD $ snap install lxd --channel 5.21/stable $ lxd init --auto $ lxc config set core.httpsaddress=127.0.0.1:8443
Use easyrsa for configuring CA: https://github.com/OpenVPN/easy-rsa $ cp -R /usr/share/easy-rsa "/tmp/pki" $ export EASYRSAKEYSIZE=4096 $ cd /tmp/pki $ ./easyrsa init-pki $ echo "lxd" | ./easyrsa build-ca nopass $ ./easyrsa build-client-full lxd-client nopass $ cp pki/ca.crt /var/snap/lxd/common/lxd/server.ca $ cp pki/issued/lxd-client.crt ~/snap/lxd/common/config/client.crt $ cp pki/private/lxd-client.key ~/snap/lxd/common/config/client.key
Restart daemon. $ systemctl reload snap.lxd.daemon
Add a restricted certificate to the trust store. $ token="$(lxc config trust add --name ca-test --quiet --restricted)" $ lxc remote add tls "${token}"
Our client has a CA-signed certificate, but it is restricted, so the client should not be able to view server config. $ lxc config get tls: core.httpsaddress 127.0.0.1:8443
Impact I believe this vulnerability is low impact because PKI mode is: 1. Not the standard or recommended mode of operation for LXD. 2. While core.trustcacertificates defaults to false, we believe that users who enable PKI mode will generally have core.trustcacertificates enabled to allow for passwordless PKI with CRL revocation (see https://github.com/canonical/lxd/issues/3832). When this mode is enabled, all clients with CA-signed certificates have root access anyway.
Note: If a restricted certificate is added before core.trustcacertificates is enabled, the certificate becomes unrestricted. We believe this was the original intention of the PR, but this should be changed to disallow any unintended permission change.
Summary If a server.ca file is present in LXDDIR at LXD start up, LXD is in "PKI mode". In this mode, only TLS clients that have a CA-signed certificate should be able to authenticate with LXD.
We have discovered that if a client that sends a non-CA signed certificate during the TLS handshake, that client is able to authenticate with LXD if their certificate is present in the trust store. - The LXD Go client (and by extension lxc) does not send non-CA signed certificates during the handshake. - A manual client (e.g. cURL) might send a non-CA signed certificate during the handshake.
Versions affected LXD 4.0 and above.
Details When PKI mode was added to LXD it was intended that all client and server certificates must be signed by the certificate authority (see https://github.com/canonical/lxd/pull/2070/commits/84d917bdcca6fe1e3191ce47f1597c7d094e1909).
In PKI mode, the TLS listener configuration is altered to add the CA certificate but the ClientAuth field of tls.Config is not changed. The ClientAuth field is set to tls.RequestClientCert, which configures the TLS connection to request a certificate from the client, but not require one. This is necessary because untrusted requests are allowed for some endpoints.
If a client certificate is present in the trust store before PKI mode is enabled, calls to LXD using that certificate fail when using the Go client for LXD. I believe that what is happening is as follows: - During the TLS handshake, the server requests a certificate from the client. The server includes in it's request a list of acceptable CAs. - The go client receives the request from the server, but does not have any certificates that match what the server requires, and so does not send any. - The server considers the handshake complete because it does not absolutely require the client certificate (see above). - In the (Daemon).Authenticate method, when checking for TLS clients, there are no PeerCertificates in the request. So util.CheckTrustState is never called and the request is denied.
Importantly, the above does not apply if the client sends a certificate during the handshake anyway. If this occurs and the certificate is present in the trust store, the request is trusted and is allowed to continue. It is possible to do this using cURL.
PoC The follow snippet demonstrates the vulnerability:
Install/initialize LXD $ snap install lxd --channel 5.21/stable $ lxd init --auto $ lxc config set core.httpsaddress=127.0.0.1:8443
Add a certificate to the trust store before enabling PKI. $ token="$(lxc config trust add --name ca-test --quiet)" $ lxc remote add tls "${token}"
Use easyrsa for configuring CA: https://github.com/OpenVPN/easy-rsa $ cp -R /usr/share/easy-rsa "/tmp/pki" $ export EASYRSAKEYSIZE=4096 $ cd /tmp/pki $ ./easyrsa init-pki $ echo "lxd" | ./easyrsa build-ca nopass $ cp pki/ca.crt /var/snap/lxd/common/lxd/server.ca
Restart daemon. $ systemctl reload snap.lxd.daemon
Using curl with the client certificate we expect a 403 Forbidden response. Instead we get a 200 OK and we are able to view the response body. $ cat ~/snap/lxd/common/config/client.crt ~/snap/lxd/common/config/client.key > ~/snap/lxd/common/config/client.pem $ curl -s --cert ~/snap/lxd/common/config/client.pem --cacert /var/snap/lxd/common/lxd/server.crt https://127.0.0.1:8443/1.0" | jq '.metadata.config."core.httpsaddress"' Impact
I believe this has a low impact for the following reasons: PKI mode is unlikely to have a large user base. PKI is likely to be configured at start up without any previous certificates in the trust store. Authentication is not bypassed entirely, the client certificate must already be trusted.
Notes I am not certain why cURL sends the certificate during the handshake but we can see it in the logs: Trying 127.0.0.1:8443... % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 Connected to 127.0.0.1 (127.0.0.1) port 8443 (#0) ALPN, offering h2 ALPN, offering http/1.1 CAfile: /var/lib/lxd/server.crt CApath: /etc/ssl/certs TLSv1.0 (OUT), TLS header, Certificate Status (22): } [5 bytes data] TLSv1.3 (OUT), TLS handshake, Client hello (1): } [512 bytes data] 0 0 0 0 0 0 0 0 --:--:-- 0:00:03 --:--:-- 0 TLSv1.2 (IN), TLS header, Certificate Status (22): { [5 bytes data] TLSv1.3 (IN), TLS handshake, Server hello (2): { [122 bytes data] TLSv1.2 (IN), TLS header, Finished (20): { [5 bytes data] TLSv1.2 (IN), TLS header, Supplemental data (23): { [5 bytes data] TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8): { [15 bytes data] TLSv1.2 (IN), TLS header, Supplemental data (23): { [5 bytes data] TLSv1.3 (IN), TLS handshake, Request CERT (13): { [69 bytes data] TLSv1.2 (IN), TLS header, Supplemental data (23): { [5 bytes data] TLSv1.3 (IN), TLS handshake, Certificate (11): { [496 bytes data] TLSv1.2 (IN), TLS header, Supplemental data (23): { [5 bytes data] TLSv1.3 (IN), TLS handshake, CERT verify (15): { [111 bytes data] TLSv1.2 (IN), TLS header, Supplemental data (23): { [5 bytes data] TLSv1.3 (IN), TLS handshake, Finished (20): { [36 bytes data] TLSv1.2 (OUT), TLS header, Finished (20): } [5 bytes data] TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1): } [1 bytes data] TLSv1.2 (OUT), TLS header, Supplemental data (23): } [5 bytes data] TLSv1.3 (OUT), TLS handshake, Certificate (11): <<<<<<<<< HERE } [455 bytes data] TLSv1.2 (OUT), TLS header, Supplemental data (23): } [5 bytes data] TLSv1.3 (OUT), TLS handshake, CERT verify (15): } [111 bytes data] TLSv1.2 (OUT), TLS header, Supplemental data (23): } [5 bytes data] TLSv1.3 (OUT), TLS handshake, Finished (20): } [36 bytes data] SSL connection using TLSv1.3 / TLSAES128GCMSHA256 ALPN, server accepted to use h2 Server certificate: subject: O=LXD; CN=root@RUBIX start date: Apr 2 15:27:39 2024 GMT expire date: Mar 31 15:27:39 2034 GMT subjectAltName: host "127.0.0.1" matched cert's IP address! issuer: O=LXD; CN=root@RUBIX SSL certificate verify ok. Using HTTP2, server supports multiplexing Connection state changed (HTTP/2 confirmed) Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0 TLSv1.2 (OUT), TLS header, Supplemental data (23): } [5 bytes data] TLSv1.2 (OUT), TLS header, Supplemental data (23): } [5 bytes data] TLSv1.2 (OUT), TLS header, Supplemental data (23): } [5 bytes data] Using Stream ID: 1 (easy handle 0x601ce9c4feb0) TLSv1.2 (OUT), TLS header, Supplemental data (23): } [5 bytes data] GET /1.0 HTTP/2 Host: 127.0.0.1:8443 user-agent: curl/7.81.0 accept: / TLSv1.2 (IN), TLS header, Supplemental data (23): { [5 bytes data] TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): { [569 bytes data] TLSv1.2 (IN), TLS header, Supplemental data (23): { [5 bytes data] Connection state changed (MAXCONCURRENTSTREAMS == 250)! TLSv1.2 (OUT), TLS header, Supplemental data (23): } [5 bytes data] TLSv1.2 (IN), TLS header, Supplemental data (23): { [5 bytes data] TLSv1.2 (IN), TLS header, Supplemental data (23): { [5 bytes data] TLSv1.2 (IN), TLS header, Supplemental data (23): { [5 bytes data] < HTTP/2 200 < content-type: application/json < etag: "a1147bd1cd26e0b98e4c4400be3c17d5de3d865a045b6e609c6a8ee1aba8c1a1" < date: Mon, 17 Jun 2024 21:25:46 GMT < TLSv1.2 (IN), TLS header, Supplemental data (23): { [5 bytes data] TLSv1.2 (IN), TLS header, Supplemental data (23): { [5 bytes data] TLSv1.2 (IN), TLS header, Supplemental data (23): { [5 bytes data] 100 11659 0 11659 0 0 3401 0 --:--:-- 0:00:03 --:--:-- 3402 Connection #0 to host 127.0.0.1 left intact
It was discovered that the getmodifiedconffiles() function in backends/packaging-apt-dpkg.py allowed injecting modified package names in a manner that would confuse the dpkg(1) call.
Last updated 25 August 2025
Last updated 25 August 2025
PackageKit provided detailed error messages to unprivileged callers that exposed information about file presence and mimetype of files that the user would be unable to determine on its own.
An integer underflow in dpdk versions before 18.11.10 and before 19.11.5 in the movedesc function can lead to large amounts of CPU cycles being eaten up in a long running loop. An attacker could cause movedesc to get stuck in a 4,294,967,295-count iteration loop. Depending on how vhostcrypto is being used this could prevent other VMs or network tasks from being serviced by the busy DPDK lcore for an extended period.
On desktop, Ubuntu UI Toolkit's StateSaver would serialise data on tmp/ files which an attacker could use to expose potentially sensitive data. StateSaver would also open files without the OEXCL flag. An attacker could exploit this to launch a symlink attack, though this is partially mitigated by symlink and hardlink restrictions in Ubuntu. Fixed in 1.1.1188+14.10.20140813.4-0ubuntu1.
Last updated 18 August 2025
Last updated 25 August 2025
A flaw was found in the Linux kernel. The generation of the device ID from the network RNG internal state is predictable. The highest threat from this vulnerability is to data confidentiality.
In FreeRDP less than or equal to 2.1.2, an integer overflow exists due to missing input sanitation in rdpegfx channel. All FreeRDP clients are affected. The input rectangles from the server are not checked against local surface coordinates and blindly accepted. A malicious server can send data that will crash the client later on (invalid length arguments to a memcpy) This has been fixed in 2.2.0. As a workaround, stop using command line arguments /gfx, /gfx-h264 and /network:auto
An assertion failure flaw was found in QEMU in the network packet processing component. This issue affects the "e1000e" and "vmxnet3" network devices. This flaw allows a malicious guest user or process to abort the QEMU process on the host, resulting in a denial of service.
addressspacemap in exec.c in QEMU 4.2.0 can trigger a NULL pointer dereference related to BounceBuffer.
In QEMU 5.0.0 and earlier, megasaslookupframe in hw/scsi/megasas.c h ...
In QEMU 5.0.0 and earlier, es1370transferaudio in hw/audio/es1370.c ...
Last updated 25 August 2025
Last updated 25 August 2025
It was discovered that the Subiquity installer for Ubuntu Server logged the LUKS full disk encryption password if one was entered.
In FreeRDP after 1.1 and before 2.0.0, a stream out-of-bounds seek in rdpreadfontcapabilityset could lead to a later out-of-bounds read. As a result, a manipulated client or server might force a disconnect due to an invalid data read. This has been fixed in 2.0.0.