See how openjpeg compares to other vendors in security performance
On 2026/10/08 00:51, TheSecguy wrote: I contacted the OpenJPEG maintainer It is unmaintained:
https://github.com/uclouvain/openjpeg/commit/06bbae8b5d5e57f8c28fe862b80a9464b320e314
"This repository should be considered unmaintained. Commits may appear from time to time depending on committer interest and mood, but no committer currently feel responsible to regularly review tickets or pull requests."
Hello,
OpenJPEG's reversible (5/3) forward wavelet transform reads and writes past the end of a heap allocation when a tile's lower resolution levels have zero height at an odd start. It is an encoder-side issue, not a decoder one: it is reached through the encoding parameters, not through a crafted input file.
A single encode usually looks fine. Repeat the same encode in one process, as any program that encodes more than one image does, and glibc's own heap checking aborts the process.
Upstream has been given the report and a patch; the four-line patch is below and in the pull request linked at the end. The repository's README currently declares the project unmaintained, so packagers may need to carry the patch themselves.
AFFECTED ========
Function: opjdwtencodeanddeinterleavev(), src/lib/openjp2/dwt.c Component: libopenjp2, the forward 5/3 (reversible) DWT, vertical pass
Tested: git master at 8314119b067c0fc77834731168daaebd379fdb12 (git describe: v2.5.4-29-g8314119b), and the same with the open pull request #1657 cherry-picked on top -- which does not change this behaviour.
Version range, from the repository's history rather than from testing: the code in question arrived with commit a38e970fa59abd796c703ec469e578b09f7ffa33, "Forward DWT 5-3: major speed up by vectorizing vertical pass" (2020-05-22), first released in 2.4.0. Running "git log -S'if (height == 0)' -- src/lib/openjp2/dwt.c" returns nothing, so no such guard has ever been in the function. That puts the code in 2.4.0, 2.5.0, 2.5.1, 2.5.2, 2.5.3, 2.5.4 and current master, and not in 2.3.1 or earlier, where the vertical pass was a different implementation that we have not examined. We did not build or run any released tarball; every measurement below is against master at 8314119b.
Not affected: the irreversible (9/7) vertical pass, opjdwtencodeanddeinterleavevreal(). At height 0 every lifting step there is inert, and the "-I" route on the same geometry is clean under AddressSanitizer.
WHAT HAPPENS ============
opjdwtencodeprocedure() runs the vertical pass once per group of up to NBELTSV8 (= 8) columns whenever the resolution level has at least one column, so a level with zero height still reaches the function with height == 0. A tile's two edges can land on the same coordinate under opjintceildivpow2() when the tile is short and the number of decomposition levels is large, which is where a zero-height level comes from.
At height == 0, sn and dn are both 0. At an even start the function does nothing. At an odd start (cascol = lcurres->y0 & 1) it takes the else branch and addresses tmp[] for two rows that do not exist -- identically in the "#ifdef SSE2" path and in the generic one. On master:
/ dwt.c:1657-1659 / for (c = 0; c < NBELTSV8; c++) { OPJSc(0) -= OPJDc(0); / 1658: reads tmp[8 + c] / }
/ dwt.c:1714-1718. (height % 2) == 0 is true at height 0, and i is 0 / / here, because the loop at 1691 is skipped when dn == 0. / if (((height) % 2) == 0) { for (c = 0; c < NBELTSV8; c++) { OPJDc(i) += (OPJSc(i) + OPJSc(i) + 2) >> 2; / 1716: reads and assigns tmp[8 + c] / } }
The scratch buffer is sized opjdwtmaxresolution(...) NBELTSV8 sizeof(OPJINT32) and allocated at dwt.c:1967-1974. Where the tile's largest resolution level is a single sample, that is 32 bytes, and tmp[8..15] is the 32 bytes immediately after it.
opjdwtdeinterleavevcols() runs zero iterations at sn == dn == 0, so nothing is written to the tile and the codestream is unaffected. A round-trip comparison therefore cannot find this.
IMPACT, AS MEASURED ===================
x86-64 Linux 6.6.87 (WSL2), Ubuntu 24.04.2 LTS, gcc 13.3.0.
1. A single encode of such a geometry exits 0 and round-trips byte-exactly. Two geometries on each of two trees, 20 runs each: 20/20 exit 0, 20/20 identical. Nothing visible happens.
2. Under AddressSanitizer (-fsanitize=address -fno-omit-frame-pointer -g, RelWithDebInfo), on master and identically with #1657 applied:
==451==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x505000000080 READ of size 4 at 0x505000000080 thread T0 #0 in opjdwtencodeanddeinterleavev .../dwt.c:1658 #1 in opjdwtencodeprocedure .../dwt.c:2016 #2 in opjtcddwtencode .../tcd.c:2574 #3 in opjtcdencodetile .../tcd.c:1507 #4 in opjj2kwritesod .../j2k.c:4931 #7 in opjj2kencode .../j2k.c:12749 #8 in main .../src/bin/jp2/opjcompress.c:2288 0x505000000080 is located 0 bytes after 32-byte region [0x505000000060,0x505000000080) allocated by thread T0 here: #0 in posixmemalign #1 in opjalignedallocn .../opjmalloc.c:61 #2 in opjaligned32malloc .../opjmalloc.c:218 #3 in opjdwtencodeprocedure .../dwt.c:1974
Rebuilt with -fsanitize-recover=address, so the process continues past the first report, it reports two out-of-bounds accesses rather than one -- the second at dwt.c:1716 -- both "READ of size 4" at 0x505000000080, the first address after the allocation. Both statements sit inside a "for (c = 0; c < NBELTSV8; c++)" loop, so on a reading of the source each should touch all eight words; we did not get the sanitizer to list them individually, and only the two above were observed.
Read versus write: the sanitizer classes both as reads and never prints a write. At 1658 the assignment target is in bounds; at 1716 gcc appears to check the read-modify-write once and class it a read. That 1716 also assigns to the out-of-bounds element is our reading of the source, and point 3 is independent evidence for it.
3. In a plain Release build, with no sanitizer at all, the same encode repeated in one process makes glibc abort the process. A program doing the 1x6 encode below eight times in one process, linked against an ordinary Release build:
master + PR 1657 master + PR 1657 + the patch -------------------------------- ---------------------------- encode 0 ok encode 0 ok Fatal glibc error: malloc.c:2599 ... encode 7 ok (sysmalloc): assertion failed all 8 encodes completed exit=134 (SIGABRT, core dumped) exit=0
The regression test in the pull request does the same thing through ctest and reports "corrupted size vs. prevsize", also with no sanitizer involved. A read cannot corrupt glibc's heap metadata, so this is tool-independent evidence that the branch writes past the allocation, which the sanitizer's classification leaves open.
So what is established is: an out-of-bounds read of heap memory immediately after a 32-byte allocation (CWE-125), an out-of-bounds write to the same place (CWE-787, from reading the source plus glibc's heap-corruption abort), and a reachable denial of service in the form of that abort.
WHAT WE DO NOT CLAIM --------------------
- We have not looked at what the overwritten bytes can be made to contain, and we make no claim about exploitability beyond the abort above. - We did not check whether any real-world encoder configuration reaches this geometry in practice; we reached it deliberately. The geometry follows from the image dimensions, the tile size and the number of decomposition levels, which are parameters the calling application sets. An application that encodes attacker-supplied dimensions with fixed tile and resolution parameters is the shape of deployment where this would matter, but we have not surveyed for one. - We tested only x86-64 Linux with gcc 13. On MSVC, opjaligned32malloc() uses alignedmalloc() rather than posixmemalign(), and we cannot say how the overrun behaves there. - We looked only at the forward transform, not the decoder.
REPRODUCER ==========
No attachment needed. Sample at (x, y) is (7x + 13y) & 0xff.
cat > makepgm.py <<'PY' import sys w, h, path = int(sys.argv[1]), int(sys.argv[2]), sys.argv[3] px = bytes(((7 x + 13 y) & 0xFF) for y in range(h) for x in range(w)) open(path, "wb").write(b"P5\n%d %d\n255\n" % (w, h) + px) PY python3 makepgm.py 1 6 in1x6.pgm
# lossless: no -r, no -q, no -I opjcompress -i in1x6.pgm -o out.j2k -t 4,5 -n 3
Against a Release build this exits 0 and the file round-trips. Against a build with -fsanitize=address it gives the report above. To see the abort with no sanitizer, do the same encode eight times in one process; the test added by the pull request does exactly that and can be run with "ctest -R emptyresolutionencode".
THE PATCH =========
Four lines, one function. It applies cleanly on plain master and on master with #1657 applied, checked with "git apply" against two fresh clones, so it can be taken either before or after #1657.
--- a/src/lib/openjp2/dwt.c +++ b/src/lib/openjp2/dwt.c @@ -1573,6 +1573,10 @@ static void opjdwtencodeanddeinterleavev( const OPJUINT32 sn = (height + (even ? 1 : 0)) >> 1; const OPJUINT32 dn = height - sn;
+ if (height == 0) { + return; + } + opjdwtfetchcolsverticalpass(arrayIn, tmpIn, height, stridewidth, cols);
At height == 0 there is nothing to transform: sn and dn are both 0, so opjdwtfetchcolsverticalpass() copies no rows, every lifting loop is empty, and opjdwtdeinterleavevcols() runs zero iterations and writes nothing. Returning immediately is equivalent to the current code in everything it produces. It is shaped and placed to match the sibling function, opjdwtencodeanddeinterleavevreal(), which already has "if (height == 1) { return; }" in that same position. One guard covers both the SSE2 and the generic path, and it cannot affect a non-empty level because the condition is exactly height == 0.
RELATION TO #1575 AND #1657 ===========================
OpenJPEG issue #1575 and the open pull request #1657 cover the other half of this, in the two horizontal passes: a reversible encode that does not round-trip when a resolution level has zero width. #1657 is two lines, by Nico Weber, and it fixes what it sets out to fix; we confirmed that.
#1657 does not touch opjdwtencodeanddeinterleavev(), and the sanitizer report above is the same with #1657 applied as without it. #1657 has been open since 2026-07-12 and is not merged at the time of writing, so a distribution carrying only #1657 still has the issue described here, and one carrying neither has both.
UPSTREAM STATUS AND TIMELINE ============================
All dates 2026, times UTC.
2026-09-27 20:22Z Reported privately by e-mail, with the analysis above, a transcript of the runs and the patch, to the committer with by far the most commits in the repository's recent history. The project documents no private route: there is no SECURITY.md, and GitHub's private vulnerability reporting is disabled for the repository. The message gave a seven-day window, said it was a default rather than a deadline, offered to wait longer or to move the whole thing into the open immediately if preferred, and said silence would not be read as consent or refusal. It also said what would follow: a public issue, a pull request carrying the patch, and this post. 2026-10-03 A short correction to one sentence of that report, and a reminder of the planned public disclosure, sent on the same thread. 2026-10-04 The window closed with no reply. 2026-10-05 Public issue, pull request and this post.
Issue: https://github.com/uclouvain/openjpeg/issues/1673 Pull request: https://github.com/uclouvain/openjpeg/pull/1674
No CVE ID yet. I am requesting one from MITRE's CNA of Last Resort, which covers projects that run no numbering authority of their own, and will reply to this message with the ID when it is assigned -- the order this list's own guidelines ask for.
The repository's README has declared the project unmaintained since 2026-07-07 ("no committer currently feel responsible to regularly review tickets or pull requests"), so the pull request may sit for a while. The patch above is four lines and stands on its own if you would rather not wait.
Regards,
-- Red Eagle Tech security () redeagle tech
openjpeg v 2.5.0 was discovered to contain a NULL pointer dereference via the component /openjp2/dwt.c.
OpenJPEG is an open-source JPEG 2000 codec. In OpenJPEG 2.5.3 and earlier, a call to opjjp2readheader may lead to OOB heap memory write when the data stream pstream is too short and pimage is not initialized.
OpenJPEG is an open-source JPEG 2000 codec. In OpenJPEG 2.5.3 and earlier, a call to opjjp2readheader may lead to OOB heap memory write when the data stream pstream is too short and pimage is not initialized.
A flaw was found in the OpenJPEG project. A heap buffer overflow condition may be triggered when certain options are specified while using the opjdecompress utility. This can lead to an application crash or other undefined behavior.
A flaw was found in the OpenJPEG project. A heap buffer overflow condition may be triggered when certain options are specified while using the opjdecompress utility. This can lead to an application crash or other undefined behavior.
It was discovered that OpenJPEG incorrectly handled certain image files. A remote attacker could possibly use this issue to cause a denial of service. CVE-2016-10506 and CVE-2017-12982 affected only Ubuntu 16.04 ESM. CVE-2018-16375, CVE-2018-20845 and CVE-2019-12973 affected only Ubuntu 18.04 ESM.
It was discovered that OpenJPEG incorrectly handled certain image data. An attacker could use this issue to cause OpenJPEG to crash, leading to a denial of service, or possibly execute arbitrary code.
It was discovered that OpenJPEG incorrectly handled certain image files. A remote attacker could possibly use this issue to cause a denial of service. (CVE-2016-9112) It was discovered that OpenJPEG did not properly handle certain input. If OpenJPEG were supplied with specially crafted input, it could be made to crash or potentially execute arbitrary code. (CVE-2018-20847, CVE-2018-21010, CVE-2020-6851, CVE-2020-8112, CVE-2020-15389) It was discovered that OpenJPEG incorrectly handled certain BMP files. A remote attacker could possibly use this issue to cause a denial of service. (CVE-2019-12973)
jp2/opjdecompress.c in OpenJPEG through 2.3.1 has a use-after-free that can be triggered if there is a mix of valid and invalid files in a directory operated on by the decompressor. Triggering a double-free may also be possible. This is related to calling opjimagedestroy twice.
Reference: https://github.com/uclouvain/openjpeg/issues/1261
A heap-based buffer overflow in the qmfbid==1 case in opjt1clbldecodeprocessor in openjp2/t1.c in OpenJPEG 2.3.1 through 2020-01-28.
Upstream Issue:
https://github.com/uclouvain/openjpeg/issues/1231
OpenJPEG through 2.3.1 has a heap-based buffer overflow in opjt1clbldecodeprocessor in libopenjp2.so.
Upstream Issue:
https://github.com/uclouvain/openjpeg/issues/1228
A flaw was found in OpenJPEG 2.3.0, there is an integer overflow caused by an out-of-bounds left shift in the opjj2ksetupencoder function (openjp2/j2k.c). Remote attackers could leverage this vulnerability to cause a denial of service via a crafted bmp file.
Reference: https://github.com/uclouvain/openjpeg/issues/1057
Double free or heap corruption vulnerability was found in opjfree function triggered by specially crafted JPEG2000 image file was found in openjpeg 2016.03.14.
CVE request (contains reproducer):
http://seclists.org/oss-sec/2016/q1/631
n our-of-bounds read vulnerability in sycc422torgb function triggered by specially crafted JPEG2000 image file was found in openjpeg version 2016.03.14.
CVE request (contains reproducer):
http://seclists.org/oss-sec/2016/q1/632
Raphael Geissert discovered out-of-bounds memory read flaws in OpenJPEG. If a specially-crafted image were opened by an application linked against OpenJPEG, it could cause the application to crash or lead to information leaks.
These issues only affected the version of OpenJPEG as shipped in Fedora (version 1.5.1)
A heap-based buffer overflow was found in the way OpenJPEG, an open-source JPEG 2000 codec written in C language, performed parsing of JPEG2000 image files. A remote attacker could provide a specially crafted JPEG 2000 file, which when opened in an application linked against openjpeg would lead to that application crash, or, potentially arbitrary code execution with the privileges of the user running the application.
Acknowledgements:
This issue was discovered by Huzaifa Sidhpurwala of the Red Hat Security Response Team.
An out-of heap-based buffer write flaw was found in the way JP2, the JPEG-2000 file format reader / writer of the OpenJPEG, an open-source JPEG 2000 codec, performed processing of palette information, present in JP2 Header Box for certain JPEG 2000 format images. A remote attacker could provide a JPEG 2000 format image file with specially-crafted value of palette index, which once processed by the JPEG 2000 decoder would leak to JPEG 2000 decoder crash, or, potentially arbitrary code execution with the privileges of the user running the decoder.
References: [1] http://technet.microsoft.com/en-us/security/msvr/msvr12-004 [2] https://secunia.com/advisories/48498/
Possible upstream patch, but it references MSVR-11-117 id instead of MSVR12-004. We need to confirm both refer to the same issue:
[3] http://code.google.com/p/openjpeg/source/detail?r=1330