Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package libaec for openSUSE:Factory checked in at 2026-05-23 23:25:22 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/libaec (Old) and /work/SRC/openSUSE:Factory/.libaec.new.2084 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "libaec" Sat May 23 23:25:22 2026 rev:12 rq:1354728 version:1.1.7 Changes: -------- --- /work/SRC/openSUSE:Factory/libaec/libaec.changes 2026-02-25 21:23:34.210269814 +0100 +++ /work/SRC/openSUSE:Factory/.libaec.new.2084/libaec.changes 2026-05-23 23:27:26.532870968 +0200 @@ -1,0 +2,6 @@ +Tue May 19 12:57:53 UTC 2026 - Manfred Schwarb <[email protected]> + +- Update to version 1.1.7: + * Fixed several security vulnerabilities discovered by libFuzzer and AI assistant + +------------------------------------------------------------------- Old: ---- libaec-v1.1.6.tar.gz New: ---- libaec-v1.1.7.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ libaec.spec ++++++ --- /var/tmp/diff_new_pack.7Zhx6r/_old 2026-05-23 23:27:27.128895299 +0200 +++ /var/tmp/diff_new_pack.7Zhx6r/_new 2026-05-23 23:27:27.132895462 +0200 @@ -17,7 +17,7 @@ Name: libaec -Version: 1.1.6 +Version: 1.1.7 Release: 0 Summary: Adaptive Entropy Coding library License: BSD-2-Clause ++++++ libaec-v1.1.6.tar.gz -> libaec-v1.1.7.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libaec-v1.1.6/CHANGELOG.md new/libaec-v1.1.7/CHANGELOG.md --- old/libaec-v1.1.6/CHANGELOG.md 2026-02-24 10:19:36.000000000 +0100 +++ new/libaec-v1.1.7/CHANGELOG.md 2026-05-19 12:20:48.000000000 +0200 @@ -1,7 +1,13 @@ # libaec Changelog All notable changes to libaec will be documented in this file. -## [1.1.6] - 2026-06-16 +## [1.1.7] - 2026-05-19 + +### Fixed +- Fixed several security vulnerabilities discovered by libFuzzer and AI + assistant + +## [1.1.6] - 2026-02-16 ### Fixed - CMake fixes by Adrien Wu diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libaec-v1.1.6/CMakeLists.txt new/libaec-v1.1.7/CMakeLists.txt --- old/libaec-v1.1.6/CMakeLists.txt 2026-02-24 10:19:36.000000000 +0100 +++ new/libaec-v1.1.7/CMakeLists.txt 2026-05-19 12:20:48.000000000 +0200 @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.26...3.31) -project(libaec LANGUAGES C VERSION 1.1.6) +project(libaec LANGUAGES C VERSION 1.1.7) option(BUILD_SHARED_LIBS "OFF: do not build shared libraries. ON (default): build shared libraries" ON) option(BUILD_STATIC_LIBS "OFF: do not build static libraries. ON (default): build static libraries" ON) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libaec-v1.1.6/configure.ac new/libaec-v1.1.7/configure.ac --- old/libaec-v1.1.6/configure.ac 2026-02-24 10:19:36.000000000 +0100 +++ new/libaec-v1.1.7/configure.ac 2026-05-19 12:20:48.000000000 +0200 @@ -2,7 +2,7 @@ m4_define([VERSION_MAJOR], [1]) m4_define([VERSION_MINOR], [1]) -m4_define([VERSION_PATCH], [6]) +m4_define([VERSION_PATCH], [7]) AC_INIT([libaec],[VERSION_MAJOR.VERSION_MINOR.VERSION_PATCH],[[email protected]]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libaec-v1.1.6/fuzzing/CMakeLists.txt new/libaec-v1.1.7/fuzzing/CMakeLists.txt --- old/libaec-v1.1.6/fuzzing/CMakeLists.txt 2026-02-24 10:19:36.000000000 +0100 +++ new/libaec-v1.1.7/fuzzing/CMakeLists.txt 2026-05-19 12:20:48.000000000 +0200 @@ -1,11 +1,13 @@ add_executable(fuzz_target fuzz_target.cc) target_link_libraries(fuzz_target PUBLIC libaec::aec) - -# Actually link libFuzzer target_link_options(fuzz_target PRIVATE -fsanitize=fuzzer) add_executable(fuzz_target_sz fuzz_target_sz.cc) target_link_libraries(fuzz_target_sz PUBLIC libaec::sz) - -# Actually link libFuzzer target_link_options(fuzz_target_sz PRIVATE -fsanitize=fuzzer) + +# Covers the streaming encode/decode APIs, RSI offset capture, +# aec_buffer_seek, and aec_decode_range. +add_executable(fuzz_target_streaming fuzz_target_streaming.cc) +target_link_libraries(fuzz_target_streaming PUBLIC libaec::aec) +target_link_options(fuzz_target_streaming PRIVATE -fsanitize=fuzzer) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libaec-v1.1.6/fuzzing/fuzz_target_streaming.cc new/libaec-v1.1.7/fuzzing/fuzz_target_streaming.cc --- old/libaec-v1.1.6/fuzzing/fuzz_target_streaming.cc 1970-01-01 01:00:00.000000000 +0100 +++ new/libaec-v1.1.7/fuzzing/fuzz_target_streaming.cc 2026-05-19 12:20:48.000000000 +0200 @@ -0,0 +1,241 @@ +/** + * @file fuzz_target_streaming.cc + * + * Exercises the streaming encode/decode APIs, RSI offset capture, + * aec_buffer_seek, and aec_decode_range — the public API functions not + * covered by fuzz_target.cc. + * + * Four phases per input: + * + * 1. Streaming decode of raw fuzz bytes on to a small output buffer. + * Forces the resumable decoder states (m_split_output, m_zero_output, + * m_se_decode, m_uncomp_copy, …). Captured RSI offsets are then used + * to exercise aec_buffer_seek. + * + * 2. Streaming encode of the fuzz payload. Captures RSI offsets via the + * encode offset API. + * + * 3. Streaming decode of the encoded output. Validates the round-trip and + * collects decode-side RSI offsets needed for phase 4. + * + * 4. aec_decode_range called with two different (pos, size) pairs using the + * offsets captured in phase 3. + * + * APIs exercised + * aec_encode_init, aec_encode_enable_offsets, aec_encode, + * aec_encode_count_offsets, aec_encode_get_offsets, aec_encode_end, + * aec_decode_init, aec_decode_enable_offsets, aec_decode, + * aec_decode_count_offsets, aec_decode_get_offsets, aec_decode_end, + * aec_decode_range, aec_buffer_seek + */ + +#include <cstddef> +#include <cstdint> +#include <cstring> +#include <vector> +#include "libaec.h" +#include <fuzzer/FuzzedDataProvider.h> + +static void fill_stream(struct aec_stream *strm, + unsigned int bits_per_sample, + unsigned int block_size, + unsigned int rsi, + unsigned int flags) +{ + memset(strm, 0, sizeof(*strm)); + strm->bits_per_sample = bits_per_sample; + strm->block_size = block_size; + strm->rsi = rsi; + strm->flags = flags; +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) +{ + FuzzedDataProvider fdp(Data, Size); + + const unsigned int bits_per_sample = + fdp.ConsumeIntegralInRange<unsigned int>(1, 32); + const unsigned int block_size = + fdp.PickValueInArray<unsigned int>({8, 16, 32, 64}); + const unsigned int rsi = + fdp.ConsumeIntegralInRange<unsigned int>(1, 4096); + /* Only the flags that aec_decode_init/aec_encode_init actually honour. */ + const unsigned int flags = + fdp.ConsumeIntegral<uint8_t>() & + (AEC_DATA_SIGNED | AEC_DATA_MSB | AEC_DATA_PREPROCESS | AEC_PAD_RSI); + + /* Output chunk size used in phase 1 to exercise resumable states. */ + const size_t chunk_out = + fdp.ConsumeIntegralInRange<size_t>(1, 128); + + std::vector<uint8_t> payload = fdp.ConsumeRemainingBytes<uint8_t>(); + if (payload.empty()) + return 0; + + /* ── Phase 1: streaming decode of raw fuzz bytes ──────────────────────── + * + * Intent: exercise the decoder on arbitrary, likely-invalid compressed + * data and verify it handles errors cleanly. The small per-call avail_out + * forces the resumable partial-output states. + * After decoding, use any captured RSI offsets with aec_buffer_seek. */ + { + struct aec_stream dec; + fill_stream(&dec, bits_per_sample, block_size, rsi, flags); + dec.next_in = payload.data(); + dec.avail_in = payload.size(); + + /* Generous output buffer; we expose it in small slices below. */ + std::vector<uint8_t> out(payload.size() * 4 + 1024); + size_t out_written = 0; + + if (aec_decode_init(&dec) == AEC_OK) { + aec_decode_enable_offsets(&dec); + + /* Feed output in chunk_out-sized pieces to hit resumable paths. */ + bool decode_error = false; + while (out_written + chunk_out <= out.size() && dec.avail_in > 0) { + dec.next_out = out.data() + out_written; + dec.avail_out = chunk_out; + int st = aec_decode(&dec, AEC_NO_FLUSH); + out_written += chunk_out - dec.avail_out; + if (st == AEC_DATA_ERROR) { decode_error = true; break; } + if (st != AEC_OK) break; + } + /* Drain whatever remains (only if no hard error above). */ + if (!decode_error && out_written < out.size()) { + dec.next_out = out.data() + out_written; + dec.avail_out = out.size() - out_written; + aec_decode(&dec, AEC_FLUSH); + } + + size_t count = 0; + if (aec_decode_count_offsets(&dec, &count) == AEC_OK && count > 0) { + std::vector<size_t> offsets(count); + if (aec_decode_get_offsets(&dec, offsets.data(), count) == AEC_OK) { + /* Exercise aec_buffer_seek at up to 4 captured offsets. */ + for (size_t i = 0; i < count && i < 4; i++) { + struct aec_stream sk; + fill_stream(&sk, bits_per_sample, block_size, rsi, flags); + sk.next_in = payload.data(); + sk.avail_in = payload.size(); + if (aec_decode_init(&sk) == AEC_OK) { + std::vector<uint8_t> sk_out(256); + sk.next_out = sk_out.data(); + sk.avail_out = sk_out.size(); + if (aec_buffer_seek(&sk, offsets[i]) == AEC_OK) + aec_decode(&sk, AEC_FLUSH); + aec_decode_end(&sk); + } + } + } + } + aec_decode_end(&dec); + } + } + + /* ── Phase 2: streaming encode of fuzz payload ────────────────────────── + * + * Intent: exercise the encoder streaming API and RSI offset capture. */ + std::vector<uint8_t> encoded(payload.size() * 2 + 1024); + size_t encoded_bytes = 0; + + { + struct aec_stream enc; + fill_stream(&enc, bits_per_sample, block_size, rsi, flags); + enc.next_in = payload.data(); + enc.avail_in = payload.size(); + enc.next_out = encoded.data(); + enc.avail_out = encoded.size(); + + if (aec_encode_init(&enc) != AEC_OK) + return 0; + aec_encode_enable_offsets(&enc); + aec_encode(&enc, AEC_FLUSH); + encoded_bytes = enc.total_out; + + size_t count = 0; + if (aec_encode_count_offsets(&enc, &count) == AEC_OK && count > 0) { + std::vector<size_t> enc_offsets(count); + aec_encode_get_offsets(&enc, enc_offsets.data(), count); + } + aec_encode_end(&enc); + } + + if (encoded_bytes == 0) + return 0; + + /* ── Phase 3: streaming decode of encoded output ──────────────────────── + * + * Intent: validate the encode→decode round-trip and collect RSI offsets + * in the decoded domain for phase 4. */ + std::vector<uint8_t> decoded(payload.size() + 1024); + size_t decoded_bytes = 0; + std::vector<size_t> dec_offsets; + + { + struct aec_stream dec; + fill_stream(&dec, bits_per_sample, block_size, rsi, flags); + dec.next_in = encoded.data(); + dec.avail_in = encoded_bytes; + dec.next_out = decoded.data(); + dec.avail_out = decoded.size(); + + if (aec_decode_init(&dec) != AEC_OK) + return 0; + aec_decode_enable_offsets(&dec); + aec_decode(&dec, AEC_FLUSH); + decoded_bytes = dec.total_out; + + size_t count = 0; + if (aec_decode_count_offsets(&dec, &count) == AEC_OK && count > 0) { + dec_offsets.resize(count); + aec_decode_get_offsets(&dec, dec_offsets.data(), count); + } + aec_decode_end(&dec); + } + + /* ── Phase 4: aec_decode_range ────────────────────────────────────────── + * + * Intent: exercise random-access decode with valid compressed data and + * captured RSI offsets. Calls the function twice: + * (a) full range from position 0 + * (b) from the start of the second RSI (if one exists) */ + if (dec_offsets.empty() || decoded_bytes == 0) + return 0; + + struct aec_stream rng; + fill_stream(&rng, bits_per_sample, block_size, rsi, flags); + rng.next_in = encoded.data(); + rng.avail_in = encoded_bytes; + + if (aec_decode_init(&rng) != AEC_OK) + return 0; + + std::vector<uint8_t> rng_out(decoded_bytes); + + /* (a) Full range. */ + rng.next_out = rng_out.data(); + rng.avail_out = decoded_bytes; + aec_decode_range(&rng, dec_offsets.data(), dec_offsets.size(), + 0, decoded_bytes); + + /* (b) Second RSI boundary onwards. */ + if (dec_offsets.size() >= 2) { + /* Mirror the bytes_per_sample calculation in aec_decode_init. + * Note: AEC_DATA_3BYTE is excluded from the flags mask above so + * bits_per_sample 17-24 always maps to 4 bytes here. */ + size_t bytes_per_sample = + (bits_per_sample > 16) ? 4 : (bits_per_sample > 8) ? 2 : 1; + size_t rsi_bytes = (size_t)rsi * block_size * bytes_per_sample; + if (rsi_bytes > 0 && rsi_bytes < decoded_bytes) { + size_t sz = decoded_bytes - rsi_bytes; + rng.next_out = rng_out.data(); + rng.avail_out = sz; + aec_decode_range(&rng, dec_offsets.data(), dec_offsets.size(), + rsi_bytes, sz); + } + } + + aec_decode_end(&rng); + return 0; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libaec-v1.1.6/src/CMakeLists.txt new/libaec-v1.1.7/src/CMakeLists.txt --- old/libaec-v1.1.6/src/CMakeLists.txt 2026-02-24 10:19:36.000000000 +0100 +++ new/libaec-v1.1.7/src/CMakeLists.txt 2026-05-19 12:20:48.000000000 +0200 @@ -43,7 +43,7 @@ # Shared libaec versioning set(libaec_VERSION_MAJOR 0) set(libaec_VERSION_MINOR 1) - set(libaec_VERSION_PATCH 6) + set(libaec_VERSION_PATCH 7) # libtool compatible versioning for Mach-O math(EXPR libaec_MACHO_COMPATIBILITY_VERSION diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libaec-v1.1.6/src/Makefile.am new/libaec-v1.1.7/src/Makefile.am --- old/libaec-v1.1.6/src/Makefile.am 2026-02-24 10:19:36.000000000 +0100 +++ new/libaec-v1.1.7/src/Makefile.am 2026-05-19 12:20:48.000000000 +0200 @@ -5,7 +5,7 @@ libaec_la_SOURCES = encode.c encode_accessors.c decode.c vector.c\ encode.h encode_accessors.h decode.h vector.h libaec_la_CPPFLAGS = $(AM_CPPFLAGS) -DLIBAEC_BUILD $(LIBAEC_SHARED) -libaec_la_LDFLAGS = -version-info 1:6:1 -no-undefined +libaec_la_LDFLAGS = -version-info 1:7:1 -no-undefined libsz_la_SOURCES = sz_compat.c libsz_la_LIBADD = libaec.la libsz_la_CPPFLAGS = $(AM_CPPFLAGS) -DLIBAEC_BUILD $(LIBAEC_SHARED) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libaec-v1.1.6/src/decode.c new/libaec-v1.1.7/src/decode.c --- old/libaec-v1.1.6/src/decode.c 2026-02-24 10:19:36.000000000 +0100 +++ new/libaec-v1.1.7/src/decode.c 2026-05-19 12:20:48.000000000 +0200 @@ -195,13 +195,22 @@ /** Get n bit from input stream - No checking whatsoever. Read bits are dumped. + Optimised fast path: no checking whatsoever. Read bits are dumped. + The caller is responsible for ensuring avail_in >= in_blklen before + invoking the fast path. As a defensive measure we clamp the byte + load to avail_in; if fewer bytes than requested are available the + accumulator is zero-padded and the returned value may be wrong, but + we will not read past the end of the buffer. */ struct internal_state *state = strm->state; if (state->bitp < n) { int b = (63 - state->bitp) >> 3; + /* Clamp to available bytes – avail_in may have been drained by + * earlier direct_get_fs calls within the same fast-path block. */ + if ((size_t)b > strm->avail_in) + b = (int)strm->avail_in; if (b == 6) { state->acc = (state->acc << 48) | ((uint64_t)strm->next_in[0] << 40) @@ -250,6 +259,9 @@ state->bitp += b << 3; } + if (state->bitp < n) + return 0; /* Ran out of input; return zero rather than undefined. */ + state->bitp -= n; return (state->acc >> state->bitp) & (UINT64_MAX >> (64 - n)); } @@ -458,6 +470,12 @@ int k = state->id - 1; size_t binary_part = (k * state->encoded_block_size) / 8 + 9; + /* Check input availability before any side effects on rsip so + * that a premature M_ERROR doesn't leave rsip partially + * advanced and corrupt the rsi_buffer on a subsequent call. */ + if (k && strm->avail_in < binary_part) + return M_ERROR; + if (state->ref) *state->rsip++ = direct_get(strm, strm->bits_per_sample); @@ -465,9 +483,6 @@ state->rsip[i] = direct_get_fs(strm) << k; if (k) { - if (strm->avail_in < binary_part) - return M_ERROR; - for (size_t i = 0; i < state->encoded_block_size; i++) *state->rsip++ += direct_get(strm, k); } else { @@ -519,6 +534,8 @@ zero_blocks--; } + if (zero_blocks > UINT32_MAX / strm->block_size) + return M_ERROR; zero_samples = zero_blocks * strm->block_size - state->ref; if (state->rsi_size - RSI_USED_SIZE(state) < zero_samples) return M_ERROR; @@ -675,7 +692,9 @@ if (strm->bits_per_sample > 32 || strm->bits_per_sample == 0 || strm->rsi == 0 + || strm->rsi > 4096 || strm->block_size & 1 + || strm->block_size > 256 || strm->block_size == 0) return AEC_CONF_ERROR; @@ -812,8 +831,15 @@ return AEC_DATA_ERROR; if (status == M_EXIT && strm->avail_out > 0 && - strm->avail_out < state->bytes_per_sample) + strm->avail_out < state->bytes_per_sample) { + /* Flush the samples already decoded into rsi_buffer so that + * next_out and avail_out remain consistent with each other. + * The caller can detect partial output via AEC_MEM_ERROR. */ + state->flush_output(strm); + strm->total_in -= strm->avail_in; + strm->total_out -= strm->avail_out; return AEC_MEM_ERROR; + } state->flush_output(strm); @@ -881,6 +907,9 @@ unsigned char *out_tmp; struct aec_stream strm_tmp = *strm; + if (strm->avail_out < size) + return AEC_MEM_ERROR; + if (state->pp) { state->ref = 1; state->encoded_block_size = strm->block_size - 1; @@ -896,6 +925,8 @@ state->mode = m_id; rsi_size = strm->rsi * strm->block_size * state->bytes_per_sample; + if (size > SIZE_MAX - (pos % rsi_size) - state->bytes_per_sample - 1) + return AEC_DATA_ERROR; rsi_n = pos / rsi_size; if (rsi_n >= rsi_offsets_count) return AEC_DATA_ERROR; @@ -908,11 +939,15 @@ return AEC_MEM_ERROR; strm_tmp.next_out = out_tmp; - if ((status = aec_buffer_seek(&strm_tmp, rsi_offsets[rsi_n])) != AEC_OK) + if ((status = aec_buffer_seek(&strm_tmp, rsi_offsets[rsi_n])) != AEC_OK) { + free(out_tmp); return status; + } - if ((status = aec_decode(&strm_tmp, AEC_FLUSH)) != 0) + if ((status = aec_decode(&strm_tmp, AEC_FLUSH)) != 0) { + free(out_tmp); return status; + } memcpy(strm->next_out, out_tmp + (pos - rsi_n * rsi_size), size); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libaec-v1.1.6/src/encode.c new/libaec-v1.1.7/src/encode.c --- old/libaec-v1.1.6/src/encode.c 2026-02-24 10:19:36.000000000 +0100 +++ new/libaec-v1.1.7/src/encode.c 2026-05-19 12:20:48.000000000 +0200 @@ -971,7 +971,8 @@ if (offsets_count < vector_size(state->offsets)) { return AEC_MEM_ERROR; } - memcpy(offsets, vector_data(state->offsets), offsets_count * sizeof(size_t)); + memcpy(offsets, vector_data(state->offsets), + vector_size(state->offsets) * sizeof(size_t)); return AEC_OK; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libaec-v1.1.6/src/sz_compat.c new/libaec-v1.1.7/src/sz_compat.c --- old/libaec-v1.1.6/src/sz_compat.c 2026-02-24 10:19:36.000000000 +0100 +++ new/libaec-v1.1.7/src/sz_compat.c 2026-05-19 12:20:48.000000000 +0200 @@ -182,6 +182,11 @@ scanlines = (sourceLen / pixel_size + param->pixels_per_scanline - 1) / param->pixels_per_scanline; padbuf_size = strm.rsi * strm.block_size * pixel_size * scanlines; + if (scanlines != 0 + && padbuf_size / scanlines != (size_t)strm.rsi * strm.block_size * pixel_size) { + status = SZ_PARAM_ERROR; + goto CLEANUP; + } padbuf = malloc(padbuf_size); if (padbuf == NULL) { status = SZ_MEM_ERROR; @@ -229,6 +234,7 @@ size_t scanlines; if (param->pixels_per_scanline == 0 + || param->pixels_per_scanline > 4096 || param->pixels_per_block == 0 || param->pixels_per_block & 1 || param->bits_per_pixel == 0
