dwa_uncompress() addresses the three colour channels by a fixed slot
index derived from nb_channels, while decode_block() extracts the planes
through channel_offsets[]. Two things follow.
The first is a wrong image for an otherwise handled file. The RLE
section, which carries the alpha channel, is written to slot 0 whenever
nb_channels is at least 4. With four or more channels and no alpha, slot
0 is blue, so the blue plane receives the RLE data. An RGB image with a
depth pass and one further channel decodes with B at 0.5498 where the
file says 0.30, and 0.5498 is the value of that unrelated channel.
Whether this happens depends on whether the encoder put a channel into
the RLE section, so the same channel list decodes correctly or loses its
blue depending on the file. Patch 1 tests the actual condition: the
alpha channel has to be the one sitting in slot 0.
The second is a channel list that the slot arithmetic cannot describe at
all, for example an RGBA image carrying a depth pass. Those decode with
the colour planes shifted by one and one plane left as it was, and
decoding reports success. Patch 2 rejects them instead.
Neither is a memory safety issue: with the pixel type check from
0469d68ac in place the slot arithmetic stays inside the line stride, so
this goes to the list and not to ffmpeg-security.
Where this sits. 7896cc67 checks the lower end of the same assumption by
rejecting fewer than three channels; patch 2 is the upper end. 6e8cf037
gates the channel list in decode_header(), but its first branch passes as
soon as R, G and B are all present, so extra channels are never looked
at, which is why an RGBA image with a depth pass gets through. The
channel ordering assumption is also the second of the two named in the
public description of CVE-2025-59733, whose pixel type half was
addressed in 0469d68ac.
The test files come out of the OpenEXR reference implementation, so they
are what a normal encoder writes rather than something crafted by hand.
Every channel is a constant plane: R 0.10, G 0.20, B 0.30, A 0.40,
Y 0.20, and every further channel 0.55.
import numpy as np, OpenEXR
H = W = 64
V = {"R": 0.10, "G": 0.20, "B": 0.30, "A": 0.40, "Y": 0.20}
def w(path, names, comp=OpenEXR.DWAA_COMPRESSION, dt=np.float16):
ch = {n: np.full((H, W), V.get(n, 0.55), dtype=dt) for n in names}
with OpenEXR.File({"compression": comp,
"type": OpenEXR.scanlineimage}, ch) as f:
f.write(path)
Decoded with
ffmpeg -hide_banner -i <file> -f rawvideo -pix_fmt gbrpf16le out.raw -y
using gbrapf16le where the file has alpha, gbrpf32le for the float file
and grayf16le for the luma file, then averaged per plane. The EXR channel
list is sorted bytewise, so uppercase names come first; the offsets below
are what decode_header() ends up with.
file channels before
after
c1 B,G,R B@0 G@2 R@4 correct
correct
c2 A,B,G,R A@0 B@2 G@4 R@6 correct
correct
c3 list of t1, but ZIP A@0 B@2 G@4 R@6 depth@8 correct
correct
c4 B,G,R as float B@0 G@4 R@8 correct
correct
c5 AO,B,G,R AO@0 B@2 G@4 R@6 correct
correct
c6 B,G,R,depth,fog B@0 G@2 R@4 depth@6 fog@8 correct
correct
c7 B,G,R and three more B@0 G@2 R@4 and three more correct
correct
r1 B,G,R,depth,zz.A B@0 G@2 R@4 depth@6 zz.A@8 B 0.5498
correct
t1 A,B,G,R,depth A@0 B@2 G@4 R@6 depth@8 R 0 G .10 B .20
refused
t2 B,G,R,depth B@0 G@2 R@4 depth@6 R .20 G .30 B 0
refused
t3 B,G,R,Z B@0 G@2 R@4 Z@6 R .20 G .30 B 0
refused
t4 Y,P,Q P@0 Q@2 Y@4 Y 0
refused
m1 no channel list none Missing channel
list, both
m2 A,Y A@0 Y@2 Gray DWA, both
m3 B,G,R as uint32 B@0 G@4 R@8 Zero ac_size, both
r1 is what patch 1 fixes: zz.A goes into the RLE section, and 0.5498 is
that channel, not blue. c5 to c7 are the cases a coarser check would
lose: they carry extra channels but keep B, G and R on the slots
dwa_uncompress() writes, and they decode correctly before and after. c3
and t1 hold the same channel list and differ only in the compression,
which places the second defect in dwa_uncompress() and not in the header
parsing. m1 to m3 are there so that the new check cannot quietly take
over a diagnostic that already exists: it runs only for three channels or
more, so Missing channel list and the Gray DWA path from 7896cc67 both
stay reachable.
Decoding t1 to t3 reports success. At the default log level the only exr
message is a warning naming the extra channel, Unsupported channel depth
for t1 and t2 and Multiple channels with index 2 for t3. Neither says
that the colour planes are wrong.
FATE is 5869 tests with 0 failures, the same before and after the series,
and the sorted list of test names is identical. 74 of those are exr tests
and none of them uses DWA compression, so no existing test changes state.
I could not add one because the samples live in the separate fate-suite.
make check does not run at this HEAD, devprogs has no rule, so the above
is make fate. Built with gcc 15 on x86_64.
Nexory (2):
avcodec/exr: Write the DWA alpha slot only when alpha occupies it
avcodec/exr: Check the DWA channel layout
libavcodec/exr.c | 26 +++++++++++++++++++++++++-
1 file changed, 25 insertions(+), 1 deletion(-)
_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]