Skip to content

Commit f863361

Browse files
authored
Clamp out-of-range unorm inputs (#651)
Clamp input data for channels specified as UNORM to be between 0 and 65535 in the internal encoding. NaNs also get replaced with zeros. This avoids undefined behavior when later storing values into an integer.
1 parent ef35af9 commit f863361

3 files changed

Lines changed: 184 additions & 2 deletions

File tree

Docs/ChangeLog-5x.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ The 5.6.0 release is a minor maintenance release.
1818
LDR image to a DDS container.
1919
* **Bug fix:** Fixed potential integer overflow when storing very large
2020
uncompressed images to a `dds` or `.ktx` output image format.
21+
* **Bug fix:** Avoid undefined behavior caused by passing floating point
22+
values outside of the [0.0, 1.0] range as data for a UNORM color channel.
23+
* **Bug fix:** Avoid undefined behavior caused by unaligned access in SSE
24+
and AVX2 SIMD library implementations.
2125

2226
<!-- ---------------------------------------------------------------------- -->
2327
## 5.5.0

Source/UnitTest/test_encode.cpp

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,4 +131,171 @@ TEST(compress, data_buffer_exceeded)
131131
astcenc_context_free(context);
132132
}
133133

134+
/** @brief Helper to run a compression on a single 4x4 block */
135+
static void run_positive_test(
136+
astcenc_profile profile,
137+
float* input
138+
) {
139+
astcenc_error status;
140+
astcenc_config config;
141+
astcenc_context* context;
142+
143+
static const astcenc_swizzle swizzle {
144+
ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A
145+
};
146+
147+
astcenc_config_init(profile, 4, 4, 1, ASTCENC_PRE_MEDIUM, 0, &config);
148+
status = astcenc_context_alloc(&config, 1, &context, nullptr);
149+
EXPECT_EQ(status, ASTCENC_SUCCESS);
150+
151+
// Single 16 byte block output data
152+
uint8_t data[16];
153+
154+
astcenc_image image;
155+
image.dim_x = 4u;
156+
image.dim_y = 4u;
157+
image.dim_z = 1u;
158+
image.data_type = ASTCENC_TYPE_F32;
159+
float* slices = input;
160+
image.data = reinterpret_cast<void**>(&slices);
161+
162+
// Should not error or trip any UBSAN errors
163+
status = astcenc_compress_image(context, &image, &swizzle, data, 16, 0);
164+
EXPECT_EQ(status, ASTCENC_SUCCESS);
165+
166+
astcenc_context_free(context);
167+
}
168+
169+
/** @brief Input data contains negative infinities. */
170+
TEST(compress, data_neg_inf_ldr)
171+
{
172+
// Cycle though a single bad value in each color channel
173+
for(int offset = 0; offset < 4; offset++)
174+
{
175+
// 4x4 block input data
176+
std::vector<float> input(4 * 4 * 4, 0.5f);
177+
input[0 + offset] = -std::numeric_limits<float>::infinity();
178+
179+
// Make data more complex to hit more compressor paths
180+
input[4 + offset] = 0.75f;
181+
input[8 + offset] = 0.35f;
182+
input[12 + offset] = 0.0f;
183+
184+
run_positive_test(ASTCENC_PRF_LDR, input.data());
185+
}
186+
}
187+
188+
/** @brief Input data contains negative infinities. */
189+
TEST(compress, data_neg_inf_hdr)
190+
{
191+
// Cycle though a single bad value in each color channel
192+
for(int offset = 0; offset < 4; offset++)
193+
{
194+
// 4x4 block input data
195+
std::vector<float> input(4 * 4 * 4, 0.5f);
196+
input[0 + offset] = -std::numeric_limits<float>::infinity();
197+
198+
run_positive_test(ASTCENC_PRF_HDR, input.data());
199+
}
200+
}
201+
202+
/** @brief Input data contains negative infinities. */
203+
TEST(compress, data_neg_inf_hdr_ldra)
204+
{
205+
// Cycle though a single bad value in each color channel
206+
for(int offset = 0; offset < 4; offset++)
207+
{
208+
// 4x4 block input data
209+
std::vector<float> input(4 * 4 * 4, 0.5f);
210+
input[0 + offset] = -std::numeric_limits<float>::infinity();
211+
212+
run_positive_test(ASTCENC_PRF_HDR_RGB_LDR_A, input.data());
213+
}
214+
}
215+
216+
/** @brief Input data contains positive infinities. */
217+
TEST(compress, data_pos_inf_ldr)
218+
{
219+
// Cycle though a single bad value in each color channel
220+
for(int offset = 0; offset < 4; offset++)
221+
{
222+
// 4x4 block input data
223+
std::vector<float> input(4 * 4 * 4, 0.5f);
224+
input[0 + offset] = std::numeric_limits<float>::infinity();
225+
226+
run_positive_test(ASTCENC_PRF_LDR, input.data());
227+
}
228+
}
229+
230+
/** @brief Input data contains positive infinities. */
231+
TEST(compress, data_pos_inf_hdr)
232+
{
233+
// Cycle though a single bad value in each color channel
234+
for(int offset = 0; offset < 4; offset++)
235+
{
236+
// 4x4 block input data
237+
std::vector<float> input(4 * 4 * 4, 0.5f);
238+
input[0 + offset] = std::numeric_limits<float>::infinity();
239+
240+
run_positive_test(ASTCENC_PRF_HDR, input.data());
241+
}
242+
}
243+
244+
/** @brief Input data contains positive infinities. */
245+
TEST(compress, data_pos_inf_hdr_ldra)
246+
{
247+
// Cycle though a single bad value in each color channel
248+
for(int offset = 0; offset < 4; offset++)
249+
{
250+
// 4x4 block input data
251+
std::vector<float> input(4 * 4 * 4, 0.5f);
252+
input[0 + offset] = std::numeric_limits<float>::infinity();
253+
254+
run_positive_test(ASTCENC_PRF_HDR_RGB_LDR_A, input.data());
255+
}
256+
}
257+
258+
259+
/** @brief Input data contains NaN. */
260+
TEST(compress, data_nan_ldr)
261+
{
262+
// Cycle though a single bad value in each color channel
263+
for(int offset = 0; offset < 4; offset++)
264+
{
265+
// 4x4 block input data
266+
std::vector<float> input(4 * 4 * 4, 0.5f);
267+
input[0 + offset] = std::numeric_limits<float>::quiet_NaN();
268+
269+
run_positive_test(ASTCENC_PRF_LDR, input.data());
270+
}
271+
}
272+
273+
/** @brief Input data contains NaN. */
274+
TEST(compress, data_nan_hdr)
275+
{
276+
// Cycle though a single bad value in each color channel
277+
for(int offset = 0; offset < 4; offset++)
278+
{
279+
// 4x4 block input data
280+
std::vector<float> input(4 * 4 * 4, 0.5f);
281+
input[0 + offset] = std::numeric_limits<float>::quiet_NaN();
282+
283+
run_positive_test(ASTCENC_PRF_HDR, input.data());
284+
}
285+
}
286+
287+
/** @brief Input data contains NaN. */
288+
TEST(compress, data_nan_hdr_ldra)
289+
{
290+
// Cycle though a single bad value in each color channel
291+
for(int offset = 0; offset < 4; offset++)
292+
{
293+
// 4x4 block input data
294+
std::vector<float> input(4 * 4 * 4, 0.5f);
295+
input[0 + offset] = std::numeric_limits<float>::quiet_NaN();
296+
297+
run_positive_test(ASTCENC_PRF_HDR_RGB_LDR_A, input.data());
298+
}
299+
}
300+
134301
}

Source/astcenc_image.cpp

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ static vfloat4 swz_texel(
121121
/**
122122
* @brief Encode a texel that is entirely LDR linear.
123123
*
124+
* Out-of-range inputs will be clamped to the valid UNORM range.
125+
*
124126
* @param data The RGBA data to encode.
125127
* @param lns_mask The mask for the HDR channels than need LNS encoding.
126128
*/
@@ -129,20 +131,29 @@ static vfloat4 encode_texel_unorm(
129131
vmask4 lns_mask
130132
) {
131133
(void)lns_mask;
132-
return data * 65535.0f;
134+
vfloat4 raw_value = data * 65535.0f;
135+
136+
// Unorm data must be in 0-1 range, so clamp because data can come from an
137+
// unconstrained float. This will replace NaNs with zero.
138+
return clamp(0.0f, 65535.0f, raw_value);
133139
}
134140

135141
/**
136142
* @brief Encode a texel that includes at least some HDR LNS texels.
137143
*
144+
* Out-of-range inputs will be clamped to the valid LNS or UNORM range,
145+
* depending on @c lns_mask.
146+
*
138147
* @param data The RGBA data to encode.
139148
* @param lns_mask The mask for the HDR channels than need LNS encoding.
140149
*/
141150
static vfloat4 encode_texel_lns(
142151
vfloat4 data,
143152
vmask4 lns_mask
144153
) {
145-
vfloat4 datav_unorm = data * 65535.0f;
154+
vfloat4 datav_unorm = encode_texel_unorm(data, lns_mask);
155+
156+
// Out-of-range inputs are clamped inside float_to_lns
146157
vfloat4 datav_lns = float_to_lns(data);
147158
return select(datav_unorm, datav_lns, lns_mask);
148159
}

0 commit comments

Comments
 (0)