Skip to content

Commit 6d9be3f

Browse files
committed
Validate DNG opcode payload sizes
DNG opcode list metadata is attacker-controlled, but the parsers read list headers and fixed-size opcode fields without first proving they are present. Unsigned size addition can also wrap around the list bounds check. Use named constants for serialized field layouts, subtraction-based bounds checks, and opcode-specific payload validation. Verify GainMap dimensions match the encoded sample count before reading.
1 parent 18cf557 commit 6d9be3f

1 file changed

Lines changed: 132 additions & 20 deletions

File tree

‎src/common/dng_opcode.c‎

Lines changed: 132 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,31 @@
2626
#define OPCODE_ID_WARP_RECTILINEAR (1)
2727
#define OPCODE_ID_VIGNETTE_RADIAL (3)
2828

29+
/* Sizes of the fixed-width values serialized in DNG opcode data. */
30+
#define DNG_UINT32_SIZE (4)
31+
#define DNG_FLOAT_SIZE (4)
32+
#define DNG_DOUBLE_SIZE (8)
33+
34+
/* DNG opcode lists start with an opcode count. Each opcode then has a header
35+
* containing four uint32 values: ID, version, flags, and parameter byte count. */
36+
#define OPCODE_LIST_HEADER_SIZE (DNG_UINT32_SIZE)
37+
#define OPCODE_HEADER_SIZE (4 * DNG_UINT32_SIZE)
38+
39+
/* A GainMap payload has eleven 32-bit fields and four 64-bit floating-point
40+
* fields before its variable-length array of gain values. */
41+
#define GAIN_MAP_HEADER_SIZE (11 * DNG_UINT32_SIZE + 4 * DNG_DOUBLE_SIZE)
42+
43+
/* WarpRectilinear has six coefficients per plane and a two-value center.
44+
* VignetteRadial has five coefficients and the same two-value center. */
45+
#define WARP_COEFFICIENTS_PER_PLANE (6)
46+
#define VIGNETTE_COEFFICIENTS (5)
47+
#define OPCODE_CENTER_VALUES (2)
48+
#define WARP_RECTILINEAR_SIZE(planes) \
49+
(DNG_UINT32_SIZE + DNG_DOUBLE_SIZE * ((planes) * WARP_COEFFICIENTS_PER_PLANE \
50+
+ OPCODE_CENTER_VALUES))
51+
#define VIGNETTE_RADIAL_SIZE \
52+
(DNG_DOUBLE_SIZE * (VIGNETTE_COEFFICIENTS + OPCODE_CENTER_VALUES))
53+
2954
static double _get_double(uint8_t *ptr)
3055
{
3156
guint64 in;
@@ -62,25 +87,58 @@ void dt_dng_opcode_process_opcode_list_2(uint8_t *buf, uint32_t buf_size, dt_ima
6287
g_list_free_full(img->dng_gain_maps, g_free);
6388
img->dng_gain_maps = NULL;
6489

90+
if(buf_size < OPCODE_LIST_HEADER_SIZE)
91+
{
92+
dt_print(DT_DEBUG_IMAGEIO, "[dng_opcode] Invalid OpcodeList2 header");
93+
return;
94+
}
95+
6596
uint32_t count = _get_long(&buf[0]);
66-
uint32_t offset = 4;
97+
uint32_t offset = OPCODE_LIST_HEADER_SIZE;
6798
while(count > 0)
6899
{
100+
if(offset > buf_size || buf_size - offset < OPCODE_HEADER_SIZE)
101+
{
102+
dt_print(DT_DEBUG_IMAGEIO, "[dng_opcode] Invalid opcode header in OpcodeList2");
103+
return;
104+
}
105+
69106
uint32_t opcode_id = _get_long(&buf[offset]);
70107
uint32_t flags = _get_long(&buf[offset + 8]);
71108
uint32_t param_size = _get_long(&buf[offset + 12]);
72-
uint8_t *param = &buf[offset + 16];
109+
uint8_t *param = &buf[offset + OPCODE_HEADER_SIZE];
73110

74-
if(offset + 16 + param_size > buf_size)
111+
if(param_size > buf_size - offset - OPCODE_HEADER_SIZE)
75112
{
76113
dt_print(DT_DEBUG_IMAGEIO, "[dng_opcode] Invalid opcode size in OpcodeList2");
77114
return;
78115
}
79116

80117
if(opcode_id == OPCODE_ID_GAINMAP)
81118
{
82-
uint32_t gain_count = (param_size - 76) / 4;
83-
dt_dng_gain_map_t *gm = g_malloc(sizeof(dt_dng_gain_map_t) + gain_count * sizeof(float));
119+
if(param_size < GAIN_MAP_HEADER_SIZE
120+
|| (param_size - GAIN_MAP_HEADER_SIZE) % DNG_FLOAT_SIZE)
121+
{
122+
dt_print(DT_DEBUG_IMAGEIO, "[dng_opcode] Invalid GainMap size in OpcodeList2");
123+
return;
124+
}
125+
126+
uint32_t gain_count = (param_size - GAIN_MAP_HEADER_SIZE) / DNG_FLOAT_SIZE;
127+
#if SIZE_MAX < UINT64_MAX
128+
if(gain_count > (SIZE_MAX - sizeof(dt_dng_gain_map_t)) / sizeof(float))
129+
{
130+
dt_print(DT_DEBUG_IMAGEIO, "[dng_opcode] GainMap is too large in OpcodeList2");
131+
return;
132+
}
133+
#endif
134+
135+
dt_dng_gain_map_t *gm =
136+
g_try_malloc(sizeof(dt_dng_gain_map_t) + gain_count * sizeof(float));
137+
if(!gm)
138+
{
139+
dt_print(DT_DEBUG_IMAGEIO, "[dng_opcode] Failed to allocate GainMap in OpcodeList2");
140+
return;
141+
}
84142
gm->top = _get_long(&param[0]);
85143
gm->left = _get_long(&param[4]);
86144
gm->bottom = _get_long(&param[8]);
@@ -96,8 +154,26 @@ void dt_dng_opcode_process_opcode_list_2(uint8_t *buf, uint32_t buf_size, dt_ima
96154
gm->map_origin_v = _get_double(&param[56]);
97155
gm->map_origin_h = _get_double(&param[64]);
98156
gm->map_planes = _get_long(&param[72]);
99-
for(int i = 0; i < gain_count; i++)
100-
gm->map_gain[i] = _get_float(&param[76 + 4*i]);
157+
158+
const uint64_t map_points =
159+
(uint64_t)gm->map_points_v * gm->map_points_h;
160+
if(gm->map_planes && map_points > UINT64_MAX / gm->map_planes)
161+
{
162+
dt_print(DT_DEBUG_IMAGEIO, "[dng_opcode] Invalid GainMap dimensions in OpcodeList2");
163+
g_free(gm);
164+
return;
165+
}
166+
167+
const uint64_t expected_gain_count = map_points * gm->map_planes;
168+
if(expected_gain_count != gain_count)
169+
{
170+
dt_print(DT_DEBUG_IMAGEIO, "[dng_opcode] Invalid GainMap data length in OpcodeList2");
171+
g_free(gm);
172+
return;
173+
}
174+
175+
for(uint32_t i = 0; i < gain_count; i++)
176+
gm->map_gain[i] = _get_float(&param[GAIN_MAP_HEADER_SIZE + DNG_FLOAT_SIZE * i]);
101177

102178
img->dng_gain_maps = g_list_append(img->dng_gain_maps, gm);
103179
}
@@ -107,7 +183,7 @@ void dt_dng_opcode_process_opcode_list_2(uint8_t *buf, uint32_t buf_size, dt_ima
107183
flags & 1 ? "optional" : "mandatory", opcode_id);
108184
}
109185

110-
offset += 16 + param_size;
186+
offset += OPCODE_HEADER_SIZE + param_size;
111187
count--;
112188
}
113189
}
@@ -118,50 +194,86 @@ void dt_dng_opcode_process_opcode_list_3(uint8_t *buf, uint32_t buf_size, dt_ima
118194
cd->dng.has_warp = FALSE;
119195
cd->dng.has_vignette = FALSE;
120196

197+
if(buf_size < OPCODE_LIST_HEADER_SIZE)
198+
{
199+
dt_print(DT_DEBUG_IMAGEIO, "[dng_opcode] Invalid OpcodeList3 header");
200+
return;
201+
}
202+
121203
uint32_t count = _get_long(&buf[0]);
122-
uint32_t offset = 4;
204+
uint32_t offset = OPCODE_LIST_HEADER_SIZE;
123205
while(count > 0)
124206
{
207+
if(offset > buf_size || buf_size - offset < OPCODE_HEADER_SIZE)
208+
{
209+
dt_print(DT_DEBUG_IMAGEIO, "[dng_opcode] Invalid opcode header in OpcodeList3");
210+
return;
211+
}
212+
125213
uint32_t opcode_id = _get_long(&buf[offset]);
126214
uint32_t flags = _get_long(&buf[offset + 8]);
127215
uint32_t param_size = _get_long(&buf[offset + 12]);
128-
uint8_t *param = &buf[offset + 16];
216+
uint8_t *param = &buf[offset + OPCODE_HEADER_SIZE];
129217

130-
if(offset + 16 + param_size > buf_size)
218+
if(param_size > buf_size - offset - OPCODE_HEADER_SIZE)
131219
{
132220
dt_print(DT_DEBUG_IMAGEIO, "[dng_opcode] Invalid opcode size in OpcodeList3");
133221
return;
134222
}
135223

136224
if(opcode_id == OPCODE_ID_WARP_RECTILINEAR)
137225
{
226+
if(param_size < DNG_UINT32_SIZE)
227+
{
228+
dt_print(DT_DEBUG_IMAGEIO, "[OPCODE_ID_WARP_RECTILINEAR] Invalid opcode size");
229+
return;
230+
}
231+
138232
const int planes = _get_long(&param[0]);
139233
if((planes != 1) && (planes != 3))
140234
{
141235
dt_print(DT_DEBUG_IMAGEIO, "[OPCODE_ID_WARP_RECTILINEAR] Invalid number of planes %i", planes);
142236
return;
143237
}
144238

239+
const uint32_t required_size = WARP_RECTILINEAR_SIZE(planes);
240+
if(param_size < required_size)
241+
{
242+
dt_print(DT_DEBUG_IMAGEIO, "[OPCODE_ID_WARP_RECTILINEAR] Invalid opcode size");
243+
return;
244+
}
245+
145246
cd->dng.planes = planes;
146247
for(int p = 0; p < planes; p++)
147248
{
148-
for(int i = 0; i < 6; i++)
149-
cd->dng.cwarp[p][i] = _get_double(&param[4 + 8 * (i + p*6)]);
249+
for(int i = 0; i < WARP_COEFFICIENTS_PER_PLANE; i++)
250+
cd->dng.cwarp[p][i] =
251+
_get_double(&param[DNG_UINT32_SIZE
252+
+ DNG_DOUBLE_SIZE * (i + p * WARP_COEFFICIENTS_PER_PLANE)]);
150253
}
151254

152-
for(int i = 0; i < 2; i++)
153-
cd->dng.centre_warp[i] = _get_double(&param[4 + 8 * (i + planes * 6)]);
255+
for(int i = 0; i < OPCODE_CENTER_VALUES; i++)
256+
cd->dng.centre_warp[i] =
257+
_get_double(&param[DNG_UINT32_SIZE
258+
+ DNG_DOUBLE_SIZE * (i + planes * WARP_COEFFICIENTS_PER_PLANE)]);
154259

155260
img->exif_correction_type = CORRECTION_TYPE_DNG;
156261
cd->dng.has_warp = TRUE;
157262
}
158263

159264
else if(opcode_id == OPCODE_ID_VIGNETTE_RADIAL)
160265
{
161-
for(int i = 0; i < 5; i++)
162-
cd->dng.cvig[i] = _get_double(&param[8 * i]);
163-
for(int i = 0; i < 2; i++)
164-
cd->dng.centre_vig[i] = _get_double(&param[8 * (5 + i)]);
266+
if(param_size < VIGNETTE_RADIAL_SIZE)
267+
{
268+
dt_print(DT_DEBUG_IMAGEIO, "[OPCODE_ID_VIGNETTE_RADIAL] Invalid opcode size");
269+
return;
270+
}
271+
272+
for(int i = 0; i < VIGNETTE_COEFFICIENTS; i++)
273+
cd->dng.cvig[i] = _get_double(&param[DNG_DOUBLE_SIZE * i]);
274+
for(int i = 0; i < OPCODE_CENTER_VALUES; i++)
275+
cd->dng.centre_vig[i] =
276+
_get_double(&param[DNG_DOUBLE_SIZE * (VIGNETTE_COEFFICIENTS + i)]);
165277

166278
cd->dng.has_vignette = TRUE;
167279
img->exif_correction_type = CORRECTION_TYPE_DNG;
@@ -173,7 +285,7 @@ void dt_dng_opcode_process_opcode_list_3(uint8_t *buf, uint32_t buf_size, dt_ima
173285
flags & 1 ? "optional" : "mandatory", opcode_id);
174286
}
175287

176-
offset += 16 + param_size;
288+
offset += OPCODE_HEADER_SIZE + param_size;
177289
count--;
178290
}
179291
}

0 commit comments

Comments
 (0)