CWE-755
DiscouragedImproper Handling of Exceptional Conditions
Abstraction: Class · Status: Incomplete
The product does not handle or incorrectly handles an exceptional condition.
739 vulnerabilities reference this CWE, most recent first.
GHSA-FPW6-HRG5-Q5X5
Vulnerability from github – Published: 2026-05-07 21:34 – Updated: 2026-07-21 14:37Summary
Access tokens created with the "never expire" option have no exp JWT claim. Three independent revocation mechanisms fail for this token type. Logout at internal/handler/auth/auth.go:154 and :163 dereferences claims.ExpiresAt.Time, panicking on the nil field so the token never hits the blacklist. RevokeToken at internal/repository/auth/auth.go:45-50 skips when remainTTL <= 0. The admin's "Delete token" panel action at internal/service/setting/access_token_service.go:183-185 removes the database record but does not call RevokeToken to blacklist the JTI. Once a never-expire token leaks, the JWT stays cryptographically valid until the admin rotates the signing key across the entire instance.
Details
Creation path at internal/util/jwt/jwt.go:103-105:
// expiry = 0 表示永不过期
if expiry > 0 {
claims.ExpiresAt = jwt.NewNumericDate(time.Now().UTC().Add(time.Duration(expiry) * time.Second))
}
For NEVER_EXPIRY, expiry = 0 and the conditional skips. The resulting JWT has no exp claim. The middleware at internal/middleware/auth.go accepts it; the jwt/v5 parser does not require exp by default.
Failure mode 1, logout panic at internal/handler/auth/auth.go:163:
// Refresh-token revocation at line 154 (safe in practice: refresh tokens always have exp).
// Access-token revocation, same pattern, at line 163 (the bug):
if claims, err := jwtUtil.ParseToken(authHeader[7:]); err == nil && claims.ID != "" {
remaining := time.Until(claims.ExpiresAt.Time) // nil deref when ExpiresAt is nil
h.authService.RevokeToken(claims.ID, remaining)
}
For a never-expire access token, claims.ExpiresAt is nil. claims.ExpiresAt.Time panics. Gin's Recovery middleware catches it and returns HTTP 500; the JTI never reaches RevokeToken. Line 154 shares the same pattern against refresh tokens, but refresh tokens are always issued with an expiry so the nil dereference does not fire there in practice.
Failure mode 2, RevokeToken skip at internal/repository/auth/auth.go:45-50:
func (authRepository *AuthRepository) RevokeToken(jti string, remainTTL time.Duration) {
if jti == "" || remainTTL <= 0 {
return
}
authRepository.cache.SetWithTTL(fmt.Sprintf("%s%s", blacklistPrefix, jti), true, 1, remainTTL)
}
Even if the logout path were patched to handle nil ExpiresAt, a caller computing remainTTL = 0 would still skip the blacklist write.
Failure mode 3, admin delete at internal/service/setting/access_token_service.go:183-185:
return settingService.transactor.Run(ctx, func(txCtx context.Context) error {
return settingService.settingRepository.DeleteAccessTokenByID(txCtx, id)
})
Deletion removes the token's metadata row from the database. No call to RevokeToken, no write to the JTI blacklist. The JWT continues to validate because the signature is still authentic and the middleware does not consult the metadata table.
The only way to invalidate a compromised never-expire token is to rotate JWT_SECRET, which invalidates every token for every user across the whole instance.
Proof of Concept
Default install. Admin creates a never-expire access token; its revocation pathways all fail:
import requests, base64, json
TARGET = "http://localhost:8300"
owner = requests.post(f"{TARGET}/api/login",
json={"username": "owner", "password": "owner-pw"}
).json()["data"]["access_token"]
# 1) Create a never-expire access token.
r = requests.post(f"{TARGET}/api/access-tokens",
headers={"Authorization": f"Bearer {owner}",
"content-type": "application/json"},
json={"name": "poc-irrevocable",
"expiry": "never",
"scopes": ["profile:read"],
"audience": "cli"})
tok = r.json()["data"]
pad = lambda s: s + "=" * (-len(s) % 4)
payload = json.loads(base64.urlsafe_b64decode(pad(tok.split(".")[1])))
print(f" exp claim: {payload.get('exp')} (None = never expires)")
print(f" jti: {payload['jti']}")
# 2) Confirm it works.
r = requests.get(f"{TARGET}/api/user", headers={"Authorization": f"Bearer {tok}"})
print(f" token -> /api/user: HTTP {r.status_code}")
# 3) Failure mode #1 — logout panics on nil ExpiresAt.
r = requests.post(f"{TARGET}/api/auth/logout",
headers={"Authorization": f"Bearer {tok}"})
print(f" logout: HTTP {r.status_code} (500 = Recovery middleware caught the panic)")
# 4) Failure mode #3 — admin delete does not blacklist the JTI.
listed = requests.get(f"{TARGET}/api/access-tokens",
headers={"Authorization": f"Bearer {owner}"}).json()["data"]
poc_row = next(t for t in listed if t["name"] == "poc-irrevocable")
r = requests.delete(f"{TARGET}/api/access-tokens/{poc_row['id']}",
headers={"Authorization": f"Bearer {owner}"})
print(f" admin delete: HTTP {r.status_code} {r.text}")
# 5) Token should now be invalid if delete blacklisted. Test it.
r = requests.get(f"{TARGET}/api/user", headers={"Authorization": f"Bearer {tok}"})
print(f" after delete, token -> /api/user: HTTP {r.status_code}")
print(f" response body: {r.text[:150]}")
Observed on v4.5.6 in the test container:
exp claim: None (None = never expires)
jti: 019daf86-6354-7c2d-9ff1-180de87667b3
token -> /api/user: HTTP 200
logout: HTTP 500 (500 = Recovery middleware caught the panic)
admin delete: HTTP 200 {"code":1,"msg":"删除访问令牌成功","data":null}
after delete, token -> /api/user: HTTP 200
response body: {"code":1,"msg":"获取用户信息成功","data":{"id":"019daf76-b5d2-7778-a90a-e943872b2946","username":"owner","email":"owner@test.local","is_admin":true,"is_owner":true,...}}
After the admin "deleted" the token, the same JWT string still returns the owner's profile data. The token stays valid with no path to invalidate it short of rotating JWT_SECRET.
Impact
The "never expire" option is intended for CLI and integration use cases where rotating tokens is expensive. When one of those tokens leaks (configuration file committed to a public repo, developer laptop compromised, log file uploaded by mistake), the admin has no remediation that does not nuke every other user's session.
A compromised token gives the attacker:
- Perpetual authenticated access at whatever scopes the token holds until the JWT secret is rotated.
- Admin's "revoke" UI button lies. The token row disappears from the panel but the bearer keeps working. The admin believes they mitigated the incident.
- Instance-wide blast radius on proper revocation. The only working fix (rotate JWT_SECRET) forces every user to log in again and invalidates every other access token. Security incidents force operators into an all-or-nothing choice.
Precondition: token theft. A stolen token is the standard threat model for any long-lived credential; the point of revocation is that stolen credentials can be invalidated. Ech0 currently has no working path to do that for the "never expire" class.
Recommended Fix
Three coordinated changes, matching the three failure modes:
- Replace "never expire" with a very long expiry (for example 10 years) so every token has a finite
expclaim. This removes the conditional atjwt.go:103entirely:
if expiry == model.NEVER_EXPIRY {
expiry = int64((10 * 365 * 24 * time.Hour).Seconds())
}
claims.ExpiresAt = jwt.NewNumericDate(time.Now().UTC().Add(time.Duration(expiry) * time.Second))
- If the "never expire" semantics must be preserved, make logout handle nil
ExpiresAtexplicitly:
if claims, err := jwtUtil.ParseToken(authHeader[7:]); err == nil && claims.ID != "" {
var remaining time.Duration
if claims.ExpiresAt != nil {
remaining = time.Until(claims.ExpiresAt.Time)
} else {
remaining = 365 * 24 * time.Hour
}
h.authService.RevokeToken(claims.ID, remaining)
}
And accept non-positive TTLs in RevokeToken by substituting a long default.
- Blacklist the JTI when admin deletes an access token:
tok, err := settingService.settingRepository.GetAccessTokenByID(ctx, id)
if err != nil {
return err
}
if tok.JTI != "" {
settingService.authRepo.RevokeToken(tok.JTI, 365*24*time.Hour)
}
return settingService.transactor.Run(ctx, func(txCtx context.Context) error {
return settingService.settingRepository.DeleteAccessTokenByID(txCtx, id)
})
Any two of the three changes close the gap; all three together make the revocation semantics match the admin's mental model.
Found by aisafe.io
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/lin-snow/ech0"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.4.8-0.20260503041146-eab62379c795"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-613",
"CWE-755"
],
"github_reviewed": true,
"github_reviewed_at": "2026-05-07T21:34:01Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "## Summary\n\nAccess tokens created with the \"never expire\" option have no `exp` JWT claim. Three independent revocation mechanisms fail for this token type. Logout at `internal/handler/auth/auth.go:154` and `:163` dereferences `claims.ExpiresAt.Time`, panicking on the nil field so the token never hits the blacklist. `RevokeToken` at `internal/repository/auth/auth.go:45-50` skips when `remainTTL \u003c= 0`. The admin\u0027s \"Delete token\" panel action at `internal/service/setting/access_token_service.go:183-185` removes the database record but does not call `RevokeToken` to blacklist the JTI. Once a never-expire token leaks, the JWT stays cryptographically valid until the admin rotates the signing key across the entire instance.\n\n## Details\n\nCreation path at `internal/util/jwt/jwt.go:103-105`:\n\n```go\n// expiry = 0 \u8868\u793a\u6c38\u4e0d\u8fc7\u671f\nif expiry \u003e 0 {\n claims.ExpiresAt = jwt.NewNumericDate(time.Now().UTC().Add(time.Duration(expiry) * time.Second))\n}\n```\n\nFor `NEVER_EXPIRY`, `expiry = 0` and the conditional skips. The resulting JWT has no `exp` claim. The middleware at `internal/middleware/auth.go` accepts it; the `jwt/v5` parser does not require `exp` by default.\n\nFailure mode 1, logout panic at `internal/handler/auth/auth.go:163`:\n\n```go\n// Refresh-token revocation at line 154 (safe in practice: refresh tokens always have exp).\n// Access-token revocation, same pattern, at line 163 (the bug):\nif claims, err := jwtUtil.ParseToken(authHeader[7:]); err == nil \u0026\u0026 claims.ID != \"\" {\n remaining := time.Until(claims.ExpiresAt.Time) // nil deref when ExpiresAt is nil\n h.authService.RevokeToken(claims.ID, remaining)\n}\n```\n\nFor a never-expire access token, `claims.ExpiresAt` is nil. `claims.ExpiresAt.Time` panics. Gin\u0027s Recovery middleware catches it and returns HTTP 500; the JTI never reaches `RevokeToken`. Line 154 shares the same pattern against refresh tokens, but refresh tokens are always issued with an expiry so the nil dereference does not fire there in practice.\n\nFailure mode 2, `RevokeToken` skip at `internal/repository/auth/auth.go:45-50`:\n\n```go\nfunc (authRepository *AuthRepository) RevokeToken(jti string, remainTTL time.Duration) {\n if jti == \"\" || remainTTL \u003c= 0 {\n return\n }\n authRepository.cache.SetWithTTL(fmt.Sprintf(\"%s%s\", blacklistPrefix, jti), true, 1, remainTTL)\n}\n```\n\nEven if the logout path were patched to handle nil `ExpiresAt`, a caller computing `remainTTL = 0` would still skip the blacklist write.\n\nFailure mode 3, admin delete at `internal/service/setting/access_token_service.go:183-185`:\n\n```go\nreturn settingService.transactor.Run(ctx, func(txCtx context.Context) error {\n return settingService.settingRepository.DeleteAccessTokenByID(txCtx, id)\n})\n```\n\nDeletion removes the token\u0027s metadata row from the database. No call to `RevokeToken`, no write to the JTI blacklist. The JWT continues to validate because the signature is still authentic and the middleware does not consult the metadata table.\n\nThe only way to invalidate a compromised never-expire token is to rotate `JWT_SECRET`, which invalidates every token for every user across the whole instance.\n\n## Proof of Concept\n\nDefault install. Admin creates a never-expire access token; its revocation pathways all fail:\n\n```python\nimport requests, base64, json\nTARGET = \"http://localhost:8300\"\n\nowner = requests.post(f\"{TARGET}/api/login\",\n json={\"username\": \"owner\", \"password\": \"owner-pw\"}\n ).json()[\"data\"][\"access_token\"]\n\n# 1) Create a never-expire access token.\nr = requests.post(f\"{TARGET}/api/access-tokens\",\n headers={\"Authorization\": f\"Bearer {owner}\",\n \"content-type\": \"application/json\"},\n json={\"name\": \"poc-irrevocable\",\n \"expiry\": \"never\",\n \"scopes\": [\"profile:read\"],\n \"audience\": \"cli\"})\ntok = r.json()[\"data\"]\npad = lambda s: s + \"=\" * (-len(s) % 4)\npayload = json.loads(base64.urlsafe_b64decode(pad(tok.split(\".\")[1])))\nprint(f\" exp claim: {payload.get(\u0027exp\u0027)} (None = never expires)\")\nprint(f\" jti: {payload[\u0027jti\u0027]}\")\n\n# 2) Confirm it works.\nr = requests.get(f\"{TARGET}/api/user\", headers={\"Authorization\": f\"Bearer {tok}\"})\nprint(f\" token -\u003e /api/user: HTTP {r.status_code}\")\n\n# 3) Failure mode #1 \u2014 logout panics on nil ExpiresAt.\nr = requests.post(f\"{TARGET}/api/auth/logout\",\n headers={\"Authorization\": f\"Bearer {tok}\"})\nprint(f\" logout: HTTP {r.status_code} (500 = Recovery middleware caught the panic)\")\n\n# 4) Failure mode #3 \u2014 admin delete does not blacklist the JTI.\nlisted = requests.get(f\"{TARGET}/api/access-tokens\",\n headers={\"Authorization\": f\"Bearer {owner}\"}).json()[\"data\"]\npoc_row = next(t for t in listed if t[\"name\"] == \"poc-irrevocable\")\nr = requests.delete(f\"{TARGET}/api/access-tokens/{poc_row[\u0027id\u0027]}\",\n headers={\"Authorization\": f\"Bearer {owner}\"})\nprint(f\" admin delete: HTTP {r.status_code} {r.text}\")\n\n# 5) Token should now be invalid if delete blacklisted. Test it.\nr = requests.get(f\"{TARGET}/api/user\", headers={\"Authorization\": f\"Bearer {tok}\"})\nprint(f\" after delete, token -\u003e /api/user: HTTP {r.status_code}\")\nprint(f\" response body: {r.text[:150]}\")\n```\n\nObserved on v4.5.6 in the test container:\n\n```\nexp claim: None (None = never expires)\njti: 019daf86-6354-7c2d-9ff1-180de87667b3\ntoken -\u003e /api/user: HTTP 200\nlogout: HTTP 500 (500 = Recovery middleware caught the panic)\nadmin delete: HTTP 200 {\"code\":1,\"msg\":\"\u5220\u9664\u8bbf\u95ee\u4ee4\u724c\u6210\u529f\",\"data\":null}\nafter delete, token -\u003e /api/user: HTTP 200\nresponse body: {\"code\":1,\"msg\":\"\u83b7\u53d6\u7528\u6237\u4fe1\u606f\u6210\u529f\",\"data\":{\"id\":\"019daf76-b5d2-7778-a90a-e943872b2946\",\"username\":\"owner\",\"email\":\"owner@test.local\",\"is_admin\":true,\"is_owner\":true,...}}\n```\n\nAfter the admin \"deleted\" the token, the same JWT string still returns the owner\u0027s profile data. The token stays valid with no path to invalidate it short of rotating `JWT_SECRET`.\n\n## Impact\n\nThe \"never expire\" option is intended for CLI and integration use cases where rotating tokens is expensive. When one of those tokens leaks (configuration file committed to a public repo, developer laptop compromised, log file uploaded by mistake), the admin has no remediation that does not nuke every other user\u0027s session.\n\nA compromised token gives the attacker:\n\n- **Perpetual authenticated access** at whatever scopes the token holds until the JWT secret is rotated.\n- **Admin\u0027s \"revoke\" UI button lies.** The token row disappears from the panel but the bearer keeps working. The admin believes they mitigated the incident.\n- **Instance-wide blast radius on proper revocation.** The only working fix (rotate JWT_SECRET) forces every user to log in again and invalidates every other access token. Security incidents force operators into an all-or-nothing choice.\n\nPrecondition: token theft. A stolen token is the standard threat model for any long-lived credential; the point of revocation is that stolen credentials can be invalidated. Ech0 currently has no working path to do that for the \"never expire\" class.\n\n## Recommended Fix\n\nThree coordinated changes, matching the three failure modes:\n\n1. Replace \"never expire\" with a very long expiry (for example 10 years) so every token has a finite `exp` claim. This removes the conditional at `jwt.go:103` entirely:\n\n```go\nif expiry == model.NEVER_EXPIRY {\n expiry = int64((10 * 365 * 24 * time.Hour).Seconds())\n}\nclaims.ExpiresAt = jwt.NewNumericDate(time.Now().UTC().Add(time.Duration(expiry) * time.Second))\n```\n\n2. If the \"never expire\" semantics must be preserved, make logout handle nil `ExpiresAt` explicitly:\n\n```go\nif claims, err := jwtUtil.ParseToken(authHeader[7:]); err == nil \u0026\u0026 claims.ID != \"\" {\n var remaining time.Duration\n if claims.ExpiresAt != nil {\n remaining = time.Until(claims.ExpiresAt.Time)\n } else {\n remaining = 365 * 24 * time.Hour\n }\n h.authService.RevokeToken(claims.ID, remaining)\n}\n```\n\nAnd accept non-positive TTLs in `RevokeToken` by substituting a long default.\n\n3. Blacklist the JTI when admin deletes an access token:\n\n```go\ntok, err := settingService.settingRepository.GetAccessTokenByID(ctx, id)\nif err != nil {\n return err\n}\nif tok.JTI != \"\" {\n settingService.authRepo.RevokeToken(tok.JTI, 365*24*time.Hour)\n}\nreturn settingService.transactor.Run(ctx, func(txCtx context.Context) error {\n return settingService.settingRepository.DeleteAccessTokenByID(txCtx, id)\n})\n```\n\nAny two of the three changes close the gap; all three together make the revocation semantics match the admin\u0027s mental model.\n\n---\n*Found by [aisafe.io](https://aisafe.io)*",
"id": "GHSA-fpw6-hrg5-q5x5",
"modified": "2026-07-21T14:37:24Z",
"published": "2026-05-07T21:34:01Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/lin-snow/Ech0/security/advisories/GHSA-fpw6-hrg5-q5x5"
},
{
"type": "WEB",
"url": "https://github.com/lin-snow/Ech0/commit/eab62379c795c3f4850a9ca938ae3f27d7171541"
},
{
"type": "PACKAGE",
"url": "https://github.com/lin-snow/Ech0"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N",
"type": "CVSS_V3"
}
],
"summary": "ech0\u0027s acess tokens with expiry=never cannot be revoked: logout panics, delete does not blacklist JTI"
}
GHSA-FQRH-W8R3-22Q6
Vulnerability from github – Published: 2022-10-11 19:00 – Updated: 2022-10-14 12:00lock order inversion in transitive grant copy handling As part of XSA-226 a missing cleanup call was inserted on an error handling path. While doing so, locking requirements were not paid attention to. As a result two cooperating guests granting each other transitive grants can cause locks to be acquired nested within one another, but in respectively opposite order. With suitable timing between the involved grant copy operations this may result in the locking up of a CPU.
{
"affected": [],
"aliases": [
"CVE-2022-33748"
],
"database_specific": {
"cwe_ids": [
"CWE-755"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2022-10-11T13:15:00Z",
"severity": "MODERATE"
},
"details": "lock order inversion in transitive grant copy handling As part of XSA-226 a missing cleanup call was inserted on an error handling path. While doing so, locking requirements were not paid attention to. As a result two cooperating guests granting each other transitive grants can cause locks to be acquired nested within one another, but in respectively opposite order. With suitable timing between the involved grant copy operations this may result in the locking up of a CPU.",
"id": "GHSA-fqrh-w8r3-22q6",
"modified": "2022-10-14T12:00:17Z",
"published": "2022-10-11T19:00:29Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2022-33748"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/TJOMUNGW6VTK5CZZRLWLVVEOUPEQBRHI"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/XWSC77GS5NATI3TT7FMVPULUPXR635XQ"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/YZVXG7OOOXCX6VIPEMLFDPIPUTFAYWPE"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/TJOMUNGW6VTK5CZZRLWLVVEOUPEQBRHI"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/XWSC77GS5NATI3TT7FMVPULUPXR635XQ"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/YZVXG7OOOXCX6VIPEMLFDPIPUTFAYWPE"
},
{
"type": "WEB",
"url": "https://security.gentoo.org/glsa/202402-07"
},
{
"type": "WEB",
"url": "https://www.debian.org/security/2022/dsa-5272"
},
{
"type": "WEB",
"url": "https://xenbits.xenproject.org/xsa/advisory-411.txt"
},
{
"type": "WEB",
"url": "http://www.openwall.com/lists/oss-security/2022/10/11/2"
},
{
"type": "WEB",
"url": "http://xenbits.xen.org/xsa/advisory-411.html"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:N/I:N/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-FRVG-XJJ8-2W58
Vulnerability from github – Published: 2023-10-10 15:30 – Updated: 2024-04-04 08:29When IPSec is configured on a Virtual Server, undisclosed traffic can cause TMM to terminate.
Note: Software versions which have reached End of Technical Support (EoTS) are not evaluated.
{
"affected": [],
"aliases": [
"CVE-2023-41085"
],
"database_specific": {
"cwe_ids": [
"CWE-755"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2023-10-10T13:15:21Z",
"severity": "HIGH"
},
"details": "\nWhen IPSec is configured on a Virtual Server, undisclosed traffic can cause TMM to terminate.\u00a0\n\nNote: Software versions which have reached End of Technical Support (EoTS) are not evaluated.\n\n\n\n",
"id": "GHSA-frvg-xjj8-2w58",
"modified": "2024-04-04T08:29:08Z",
"published": "2023-10-10T15:30:50Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2023-41085"
},
{
"type": "WEB",
"url": "https://my.f5.com/manage/s/article/K000132420"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-FVC9-Q887-5P2H
Vulnerability from github – Published: 2026-04-14 18:30 – Updated: 2026-06-30 03:36Concurrent execution using shared resource with improper synchronization ('race condition') in .NET Framework allows an unauthorized attacker to deny service over a network.
{
"affected": [],
"aliases": [
"CVE-2026-23666"
],
"database_specific": {
"cwe_ids": [
"CWE-366",
"CWE-755"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-04-14T18:16:44Z",
"severity": "HIGH"
},
"details": "Concurrent execution using shared resource with improper synchronization (\u0027race condition\u0027) in .NET Framework allows an unauthorized attacker to deny service over a network.",
"id": "GHSA-fvc9-q887-5p2h",
"modified": "2026-06-30T03:36:16Z",
"published": "2026-04-14T18:30:37Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-23666"
},
{
"type": "WEB",
"url": "https://access.redhat.com/security/cve/CVE-2026-23666"
},
{
"type": "WEB",
"url": "https://bugzilla.redhat.com/show_bug.cgi?id=2458271"
},
{
"type": "WEB",
"url": "https://msrc.microsoft.com/update-guide/vulnerability/CVE-2026-23666"
},
{
"type": "WEB",
"url": "https://security.access.redhat.com/data/csaf/v2/vex/2026/cve-2026-23666.json"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-FWHH-8J4M-VVW8
Vulnerability from github – Published: 2022-01-04 00:01 – Updated: 2022-01-13 00:01Possible denial of service due to improper handling of debug register trap from user applications in Snapdragon Consumer IOT, Snapdragon Industrial IOT, Snapdragon Mobile
{
"affected": [],
"aliases": [
"CVE-2021-30283"
],
"database_specific": {
"cwe_ids": [
"CWE-755"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2022-01-03T08:15:00Z",
"severity": "MODERATE"
},
"details": "Possible denial of service due to improper handling of debug register trap from user applications in Snapdragon Consumer IOT, Snapdragon Industrial IOT, Snapdragon Mobile",
"id": "GHSA-fwhh-8j4m-vvw8",
"modified": "2022-01-13T00:01:43Z",
"published": "2022-01-04T00:01:00Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2021-30283"
},
{
"type": "WEB",
"url": "https://www.qualcomm.com/company/product-security/bulletins/december-2021-bulletin"
}
],
"schema_version": "1.4.0",
"severity": []
}
GHSA-FX5J-QCQG-GRPF
Vulnerability from github – Published: 2026-09-10 15:08 – Updated: 2026-09-10 15:08Negative shared-string index causes panic in GetCellValue and GetRows
Summary
Excelize parses shared-string cell values with strconv.Atoi and checks only the upper bound before indexing the shared string slice. If an XLSX file contains a shared-string cell with <v>-1</v>, the parsed index is negative. The upper-bound check still passes (len(sharedStrings) > -1), and Excelize indexes sharedStrings[-1], causing a runtime panic.
This was reproduced on the current default branch commit 1213a8bd7c5ab360554603ac5c995ccaf6eb4314 and the latest release tag v2.10.1 (5ad5ab3af0054c55bdce09f1530085600e9f2e45). The issue is independent from the row-bound allocation report, so I am reporting it separately.
Affected package
- Package:
github.com/xuri/excelize/v2 - Tested affected versions: current default branch at
1213a8bd7c5ab360554603ac5c995ccaf6eb4314, and releasev2.10.1 - Fixed version: none known at the time of this report
Impact
An attacker who can provide an XLSX file to an application using Excelize can trigger a process panic when the application reads the malicious cell through common APIs such as GetCellValue or GetRows. In services that parse untrusted spreadsheets without a panic recovery boundary, this can cause denial of service.
Root cause
For shared-string cells (t="s"), xlsxC.getValueFrom() parses the cell value as a shared-string index and only checks whether the index is below len(d.SI) before indexing:
xlsxSI, _ := strconv.Atoi(strings.TrimSpace(c.V))
if len(d.SI) > xlsxSI {
return d.SI[xlsxSI].String(), nil
}
For xlsxSI == -1, len(d.SI) > -1 is true, so the code proceeds to index d.SI[-1] and panics.
Minimal worksheet payload
<?xml version="1.0" encoding="UTF-8"?>
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<sheetData>
<row r="1"><c r="A1" t="s"><v>-1</v></c></row>
</sheetData>
</worksheet>
The workbook also contains a normal sharedStrings.xml with one string (ok), so the failure is specifically due to accepting a negative index.
Reproduction
Calling GetCellValue("Sheet1", "A1") on the workbook panics:
== negative shared string GetCellValue ==
elapsed=0s alloc_delta=0MB
PANIC: runtime.boundsError runtime error: index out of range [-1]
Calling GetRows("Sheet1") on the same workbook also panics:
== negative shared string GetRows ==
elapsed=0s alloc_delta=0MB
PANIC: runtime.boundsError runtime error: index out of range [-1]
The same results were observed on current default branch commit 1213a8bd7c5ab360554603ac5c995ccaf6eb4314 and on release v2.10.1.
Expected behavior
Malformed shared-string indices should be rejected or treated as missing/invalid string references without panicking.
Suggested remediation
Check both lower and upper bounds before indexing the shared string table. For example:
if xlsxSI >= 0 && xlsxSI < len(d.SI) {
return d.SI[xlsxSI].String(), nil
}
Add regression tests for GetCellValue() and GetRows() on t="s" cells whose <v> value is negative.
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/xuri/excelize/v2"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.11.0"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Go",
"name": "github.com/xuri/excelize"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"last_affected": "1.4.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-59162"
],
"database_specific": {
"cwe_ids": [
"CWE-248",
"CWE-755"
],
"github_reviewed": true,
"github_reviewed_at": "2026-09-10T15:08:53Z",
"nvd_published_at": "2026-07-10T17:17:02Z",
"severity": "MODERATE"
},
"details": "# Negative shared-string index causes panic in GetCellValue and GetRows\n\n## Summary\n\nExcelize parses shared-string cell values with `strconv.Atoi` and checks only the upper bound before indexing the shared string slice. If an XLSX file contains a shared-string cell with `\u003cv\u003e-1\u003c/v\u003e`, the parsed index is negative. The upper-bound check still passes (`len(sharedStrings) \u003e -1`), and Excelize indexes `sharedStrings[-1]`, causing a runtime panic.\n\nThis was reproduced on the current default branch commit `1213a8bd7c5ab360554603ac5c995ccaf6eb4314` and the latest release tag `v2.10.1` (`5ad5ab3af0054c55bdce09f1530085600e9f2e45`). The issue is independent from the row-bound allocation report, so I am reporting it separately.\n\n## Affected package\n\n- Package: `github.com/xuri/excelize/v2`\n- Tested affected versions: current default branch at `1213a8bd7c5ab360554603ac5c995ccaf6eb4314`, and release `v2.10.1`\n- Fixed version: none known at the time of this report\n\n## Impact\n\nAn attacker who can provide an XLSX file to an application using Excelize can trigger a process panic when the application reads the malicious cell through common APIs such as `GetCellValue` or `GetRows`. In services that parse untrusted spreadsheets without a panic recovery boundary, this can cause denial of service.\n\n## Root cause\n\nFor shared-string cells (`t=\"s\"`), `xlsxC.getValueFrom()` parses the cell value as a shared-string index and only checks whether the index is below `len(d.SI)` before indexing:\n\n```go\nxlsxSI, _ := strconv.Atoi(strings.TrimSpace(c.V))\nif len(d.SI) \u003e xlsxSI {\n return d.SI[xlsxSI].String(), nil\n}\n```\n\nFor `xlsxSI == -1`, `len(d.SI) \u003e -1` is true, so the code proceeds to index `d.SI[-1]` and panics.\n\n## Minimal worksheet payload\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003cworksheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\"\u003e\n \u003csheetData\u003e\n \u003crow r=\"1\"\u003e\u003cc r=\"A1\" t=\"s\"\u003e\u003cv\u003e-1\u003c/v\u003e\u003c/c\u003e\u003c/row\u003e\n \u003c/sheetData\u003e\n\u003c/worksheet\u003e\n```\n\nThe workbook also contains a normal `sharedStrings.xml` with one string (`ok`), so the failure is specifically due to accepting a negative index.\n\n## Reproduction\n\nCalling `GetCellValue(\"Sheet1\", \"A1\")` on the workbook panics:\n\n```text\n== negative shared string GetCellValue ==\nelapsed=0s alloc_delta=0MB\nPANIC: runtime.boundsError runtime error: index out of range [-1]\n```\n\nCalling `GetRows(\"Sheet1\")` on the same workbook also panics:\n\n```text\n== negative shared string GetRows ==\nelapsed=0s alloc_delta=0MB\nPANIC: runtime.boundsError runtime error: index out of range [-1]\n```\n\nThe same results were observed on current default branch commit `1213a8bd7c5ab360554603ac5c995ccaf6eb4314` and on release `v2.10.1`.\n\n## Expected behavior\n\nMalformed shared-string indices should be rejected or treated as missing/invalid string references without panicking.\n\n## Suggested remediation\n\nCheck both lower and upper bounds before indexing the shared string table. For example:\n\n```go\nif xlsxSI \u003e= 0 \u0026\u0026 xlsxSI \u003c len(d.SI) {\n return d.SI[xlsxSI].String(), nil\n}\n```\n\nAdd regression tests for `GetCellValue()` and `GetRows()` on `t=\"s\"` cells whose `\u003cv\u003e` value is negative.",
"id": "GHSA-fx5j-qcqg-grpf",
"modified": "2026-09-10T15:08:53Z",
"published": "2026-09-10T15:08:53Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/qax-os/excelize/security/advisories/GHSA-fx5j-qcqg-grpf"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-59162"
},
{
"type": "WEB",
"url": "https://github.com/qax-os/excelize/pull/2331"
},
{
"type": "WEB",
"url": "https://github.com/qax-os/excelize/commit/93f0b3caed37f21ef5079e3259c6c21dcfe68453"
},
{
"type": "PACKAGE",
"url": "https://github.com/qax-os/excelize"
},
{
"type": "WEB",
"url": "https://github.com/qax-os/excelize/releases/tag/v2.11.0"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "Excelize: Negative shared-string index causes panic in GetCellValue and GetRows"
}
GHSA-G2V8-RP7F-83RH
Vulnerability from github – Published: 2022-05-24 19:16 – Updated: 2022-05-24 19:16Assuming a shell privilege is gained, an improper exception handling for multi_sim_bar_hide_by_meadia_full value in SystemUI prior to SMR Oct-2021 Release 1 allows an attacker to cause a permanent denial of service in user device before factory reset.
{
"affected": [],
"aliases": [
"CVE-2021-25473"
],
"database_specific": {
"cwe_ids": [
"CWE-755"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2021-10-06T18:15:00Z",
"severity": "MODERATE"
},
"details": "Assuming a shell privilege is gained, an improper exception handling for multi_sim_bar_hide_by_meadia_full value in SystemUI prior to SMR Oct-2021 Release 1 allows an attacker to cause a permanent denial of service in user device before factory reset.",
"id": "GHSA-g2v8-rp7f-83rh",
"modified": "2022-05-24T19:16:44Z",
"published": "2022-05-24T19:16:44Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2021-25473"
},
{
"type": "WEB",
"url": "https://security.samsungmobile.com/securityUpdate.smsb?year=2021\u0026month=10"
}
],
"schema_version": "1.4.0",
"severity": []
}
GHSA-G2WC-3MQ6-J4GX
Vulnerability from github – Published: 2022-05-13 01:05 – Updated: 2022-05-13 01:05A vulnerability in the web management interface of Cisco Wireless LAN Controller (WLC) Software could allow an unauthenticated, remote attacker to cause a denial of service (DoS) condition on an affected device. The vulnerability is due to a missing internal handler for the specific request. An attacker could exploit this vulnerability by accessing a specific hidden URL on the GUI web management interface. A successful exploit could allow the attacker to cause a reload of the device, resulting in a DoS condition. This vulnerability affects only the Cisco Wireless LAN Controller 8.3.102.0 release. Cisco Bug IDs: CSCvb48198.
{
"affected": [],
"aliases": [
"CVE-2017-3832"
],
"database_specific": {
"cwe_ids": [
"CWE-755"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2017-04-06T18:59:00Z",
"severity": "HIGH"
},
"details": "A vulnerability in the web management interface of Cisco Wireless LAN Controller (WLC) Software could allow an unauthenticated, remote attacker to cause a denial of service (DoS) condition on an affected device. The vulnerability is due to a missing internal handler for the specific request. An attacker could exploit this vulnerability by accessing a specific hidden URL on the GUI web management interface. A successful exploit could allow the attacker to cause a reload of the device, resulting in a DoS condition. This vulnerability affects only the Cisco Wireless LAN Controller 8.3.102.0 release. Cisco Bug IDs: CSCvb48198.",
"id": "GHSA-g2wc-3mq6-j4gx",
"modified": "2022-05-13T01:05:20Z",
"published": "2022-05-13T01:05:20Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2017-3832"
},
{
"type": "WEB",
"url": "https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20170405-wlc3"
},
{
"type": "WEB",
"url": "http://www.securityfocus.com/bid/97421"
},
{
"type": "WEB",
"url": "http://www.securitytracker.com/id/1038184"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-G5VG-RVQ4-MQ5P
Vulnerability from github – Published: 2024-04-17 18:31 – Updated: 2024-04-29 21:30In the Linux kernel, the following vulnerability has been resolved:
drm/buddy: Fix alloc_range() error handling code
Few users have observed display corruption when they boot the machine to KDE Plasma or playing games. We have root caused the problem that whenever alloc_range() couldn't find the required memory blocks the function was returning SUCCESS in some of the corner cases.
The right approach would be if the total allocated size is less than the required size, the function should return -ENOSPC.
{
"affected": [],
"aliases": [
"CVE-2024-26911"
],
"database_specific": {
"cwe_ids": [
"CWE-755"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2024-04-17T16:15:07Z",
"severity": "LOW"
},
"details": "In the Linux kernel, the following vulnerability has been resolved:\n\ndrm/buddy: Fix alloc_range() error handling code\n\nFew users have observed display corruption when they boot\nthe machine to KDE Plasma or playing games. We have root\ncaused the problem that whenever alloc_range() couldn\u0027t\nfind the required memory blocks the function was returning\nSUCCESS in some of the corner cases.\n\nThe right approach would be if the total allocated size\nis less than the required size, the function should\nreturn -ENOSPC.",
"id": "GHSA-g5vg-rvq4-mq5p",
"modified": "2024-04-29T21:30:34Z",
"published": "2024-04-17T18:31:32Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-26911"
},
{
"type": "WEB",
"url": "https://git.kernel.org/stable/c/4b59c3fada06e5e8010ef7700689c71986e667a2"
},
{
"type": "WEB",
"url": "https://git.kernel.org/stable/c/8746c6c9dfa31d269c65dd52ab42fde0720b7d91"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-G77H-45RF-HCX4
Vulnerability from github – Published: 2026-07-17 20:19 – Updated: 2026-07-17 20:19Summary
ExifReader 4.40.0 can throw an uncaught RangeError: Offset is outside the bounds of the DataView while parsing crafted HEIC/AVIF files. The file only needs a valid leading ftyp box with a HEIC/AVIF major brand followed by a malformed ISO-BMFF box, such as an empty 8-byte free box or a truncated extended-size box.
This is reachable through the public ExifReader.load() API for in-memory buffers and through the async file/URL loaders when an application parses attacker-supplied images. In applications that do not wrap every parse in a defensive try/catch, a single uploaded or fetched image can abort the request/worker and cause a denial of service.
Credit requested: Yaohui Wang.
Affected version tested
- npm package:
exifreader - Version:
4.40.0 - Repository commit tested:
8cb0261a26b7d986955fe0a6780f076dcb7902e7
Root cause
The ISO-BMFF parser assumes that every top-level box with at least an 8-byte header also has enough bytes for the fields required by its parsed form. In src/image-header-iso-bmff.js:
findMetaBox()callsparseBox(dataView, offset)while only checking thatoffset + 8 <= dataView.byteLength.parseBox()callsgetBoxLength()and then unconditionally reads fields such as the full-box version byte formeta/iloc/iinf/idatboxes.getBoxLength()handlesboxLength === 1by callinghasEmptyHighBits(dataView, offset), which readsdataView.getUint32(offset + 8)without first checking that the 64-bit extended size field is present.
As a result, syntactically small or truncated boxes after a valid HEIC/AVIF ftyp box escape the format-detection catch blocks and throw from the main parsing path.
Reproduction
Run this from the repository root against the committed dist/exif-reader.js bundle:
const ExifReader = require('./dist/exif-reader.js');
function u32be(n) {
return [(n >>> 24) & 255, (n >>> 16) & 255, (n >>> 8) & 255, n & 255];
}
function ascii(s) {
return Array.from(Buffer.from(s, 'ascii'));
}
function box(type, content = []) {
return [...u32be(8 + content.length), ...ascii(type), ...content];
}
for (const brand of ['heic', 'avif']) {
for (const badBox of ['free', 'abcd']) {
const bytes = Uint8Array.from([
...box('ftyp', ascii(brand)),
...box(badBox), // 8-byte box header with no content
]);
try {
ExifReader.load(bytes.buffer);
console.log(`${brand}/${badBox}: no throw`);
} catch (e) {
console.log(`${brand}/${badBox}: ${e.name}: ${e.message}`);
console.log(String(e.stack).split('\n').slice(0, 6).join('\n'));
}
}
}
Observed output on Node v23.11.0 with ExifReader 4.40.0:
heic/free: RangeError: Offset is outside the bounds of the DataView
RangeError: Offset is outside the bounds of the DataView
at DataView.prototype.getUint8 (<anonymous>)
at parseBox (.../dist/exif-reader.js:1:16513)
at findMetaBox (.../dist/exif-reader.js:1:19032)
at findOffsets (.../dist/exif-reader.js:1:19101)
heic/abcd: RangeError: Offset is outside the bounds of the DataView
avif/free: RangeError: Offset is outside the bounds of the DataView
avif/abcd: RangeError: Offset is outside the bounds of the DataView
A second variant triggers the extended-size path:
const truncatedExtendedBox = [...u32be(1), ...ascii('free')];
const heic = Uint8Array.from([...box('ftyp', ascii('heic')), ...truncatedExtendedBox]);
ExifReader.load(heic.buffer);
That throws from hasEmptyHighBits() / getBoxLength() because the extended-size high/low fields are not present.
Expected behavior
Malformed/truncated metadata boxes should be handled like other malformed metadata in the project: return only the successfully parsed file type/metadata, return no app markers, or throw a controlled project-specific error. A safe JavaScript bounds error should not escape from the parser for an attacker-controlled image container.
Security impact
This is a denial-of-service issue for services that parse user-provided HEIC/AVIF files with ExifReader. A minimal attacker-controlled image buffer can cause an unhandled exception in the parser and abort the surrounding request/worker if the embedding application does not catch every parse error.
Suggested severity: Medium. Suggested CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L.
Suggested fix
Add explicit bounds checks before every DataView read in the ISO-BMFF box parser, especially:
- before reading the 64-bit extended size fields in
getBoxLength(); - before reading the full-box version byte in
parseBox(); - before descending into
parseSubBoxes()when a declared box length exceeds available bytes; - ensure
findMetaBox()breaks on boxes whose declared length is invalid or not fully present.
A regression test should cover ftyp/heic and ftyp/avif followed by an 8-byte empty free/unknown box and by a truncated extended-size box.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.40.0"
},
"package": {
"ecosystem": "npm",
"name": "exifreader"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.40.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-53496"
],
"database_specific": {
"cwe_ids": [
"CWE-248",
"CWE-755"
],
"github_reviewed": true,
"github_reviewed_at": "2026-07-17T20:19:39Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "## Summary\n\nExifReader 4.40.0 can throw an uncaught `RangeError: Offset is outside the bounds of the DataView` while parsing crafted HEIC/AVIF files. The file only needs a valid leading `ftyp` box with a HEIC/AVIF major brand followed by a malformed ISO-BMFF box, such as an empty 8-byte `free` box or a truncated extended-size box.\n\nThis is reachable through the public `ExifReader.load()` API for in-memory buffers and through the async file/URL loaders when an application parses attacker-supplied images. In applications that do not wrap every parse in a defensive try/catch, a single uploaded or fetched image can abort the request/worker and cause a denial of service.\n\nCredit requested: Yaohui Wang.\n\n## Affected version tested\n\n- npm package: `exifreader`\n- Version: `4.40.0`\n- Repository commit tested: `8cb0261a26b7d986955fe0a6780f076dcb7902e7`\n\n## Root cause\n\nThe ISO-BMFF parser assumes that every top-level box with at least an 8-byte header also has enough bytes for the fields required by its parsed form. In `src/image-header-iso-bmff.js`:\n\n- `findMetaBox()` calls `parseBox(dataView, offset)` while only checking that `offset + 8 \u003c= dataView.byteLength`.\n- `parseBox()` calls `getBoxLength()` and then unconditionally reads fields such as the full-box version byte for `meta`/`iloc`/`iinf`/`idat` boxes.\n- `getBoxLength()` handles `boxLength === 1` by calling `hasEmptyHighBits(dataView, offset)`, which reads `dataView.getUint32(offset + 8)` without first checking that the 64-bit extended size field is present.\n\nAs a result, syntactically small or truncated boxes after a valid HEIC/AVIF `ftyp` box escape the format-detection catch blocks and throw from the main parsing path.\n\n## Reproduction\n\nRun this from the repository root against the committed `dist/exif-reader.js` bundle:\n\n```js\nconst ExifReader = require(\u0027./dist/exif-reader.js\u0027);\n\nfunction u32be(n) {\n return [(n \u003e\u003e\u003e 24) \u0026 255, (n \u003e\u003e\u003e 16) \u0026 255, (n \u003e\u003e\u003e 8) \u0026 255, n \u0026 255];\n}\nfunction ascii(s) {\n return Array.from(Buffer.from(s, \u0027ascii\u0027));\n}\nfunction box(type, content = []) {\n return [...u32be(8 + content.length), ...ascii(type), ...content];\n}\n\nfor (const brand of [\u0027heic\u0027, \u0027avif\u0027]) {\n for (const badBox of [\u0027free\u0027, \u0027abcd\u0027]) {\n const bytes = Uint8Array.from([\n ...box(\u0027ftyp\u0027, ascii(brand)),\n ...box(badBox), // 8-byte box header with no content\n ]);\n\n try {\n ExifReader.load(bytes.buffer);\n console.log(`${brand}/${badBox}: no throw`);\n } catch (e) {\n console.log(`${brand}/${badBox}: ${e.name}: ${e.message}`);\n console.log(String(e.stack).split(\u0027\\n\u0027).slice(0, 6).join(\u0027\\n\u0027));\n }\n }\n}\n```\n\nObserved output on Node v23.11.0 with ExifReader 4.40.0:\n\n```text\nheic/free: RangeError: Offset is outside the bounds of the DataView\nRangeError: Offset is outside the bounds of the DataView\n at DataView.prototype.getUint8 (\u003canonymous\u003e)\n at parseBox (.../dist/exif-reader.js:1:16513)\n at findMetaBox (.../dist/exif-reader.js:1:19032)\n at findOffsets (.../dist/exif-reader.js:1:19101)\n\nheic/abcd: RangeError: Offset is outside the bounds of the DataView\navif/free: RangeError: Offset is outside the bounds of the DataView\navif/abcd: RangeError: Offset is outside the bounds of the DataView\n```\n\nA second variant triggers the extended-size path:\n\n```js\nconst truncatedExtendedBox = [...u32be(1), ...ascii(\u0027free\u0027)];\nconst heic = Uint8Array.from([...box(\u0027ftyp\u0027, ascii(\u0027heic\u0027)), ...truncatedExtendedBox]);\nExifReader.load(heic.buffer);\n```\n\nThat throws from `hasEmptyHighBits()` / `getBoxLength()` because the extended-size high/low fields are not present.\n\n## Expected behavior\n\nMalformed/truncated metadata boxes should be handled like other malformed metadata in the project: return only the successfully parsed file type/metadata, return no app markers, or throw a controlled project-specific error. A safe JavaScript bounds error should not escape from the parser for an attacker-controlled image container.\n\n## Security impact\n\nThis is a denial-of-service issue for services that parse user-provided HEIC/AVIF files with ExifReader. A minimal attacker-controlled image buffer can cause an unhandled exception in the parser and abort the surrounding request/worker if the embedding application does not catch every parse error.\n\nSuggested severity: Medium. Suggested CVSS: `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L`.\n\n## Suggested fix\n\nAdd explicit bounds checks before every `DataView` read in the ISO-BMFF box parser, especially:\n\n- before reading the 64-bit extended size fields in `getBoxLength()`;\n- before reading the full-box version byte in `parseBox()`;\n- before descending into `parseSubBoxes()` when a declared box length exceeds available bytes;\n- ensure `findMetaBox()` breaks on boxes whose declared length is invalid or not fully present.\n\nA regression test should cover `ftyp/heic` and `ftyp/avif` followed by an 8-byte empty `free`/unknown box and by a truncated extended-size box.",
"id": "GHSA-g77h-45rf-hcx4",
"modified": "2026-07-17T20:19:39Z",
"published": "2026-07-17T20:19:39Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/mattiasw/ExifReader/security/advisories/GHSA-g77h-45rf-hcx4"
},
{
"type": "PACKAGE",
"url": "https://github.com/mattiasw/ExifReader"
},
{
"type": "WEB",
"url": "https://github.com/mattiasw/ExifReader/releases/tag/v4.40.1"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L",
"type": "CVSS_V3"
}
],
"summary": "ExifReader HEIC/AVIF ISO-BMFF parser throws uncaught RangeError on truncated boxes"
}
No mitigation information available for this CWE.
No CAPEC attack patterns related to this CWE.