Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions .github/workflows/security-regressions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Security regression tests
on:
pull_request:
branches: [master]
paths:
- 'Tests/CodeScanning/**'
- 'third-party/**'
- 'Telegram/WatchApp/Packages/**'
- 'submodules/CompanionRenderer/**'
- 'submodules/ffmpeg/**'
- 'submodules/TelegramUI/Components/RichTextEditor/Host/Engine/Scripts/ime_keyboard_contract.py'
- 'submodules/TelegramUI/Components/RichTextEditor/Host/Engine/Scripts/tests/test_ime_keyboard_contract.py'
- '.github/workflows/security-regressions.yml'
push:
branches: [master]
paths:
- 'Tests/CodeScanning/**'
- 'third-party/**'
- 'Telegram/WatchApp/Packages/**'
- 'submodules/CompanionRenderer/**'
- 'submodules/ffmpeg/**'
- 'submodules/TelegramUI/Components/RichTextEditor/Host/Engine/Scripts/ime_keyboard_contract.py'
- 'submodules/TelegramUI/Components/RichTextEditor/Host/Engine/Scripts/tests/test_ime_keyboard_contract.py'
- '.github/workflows/security-regressions.yml'
permissions:
contents: read
jobs:
parser:
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Escaped and adversarial keyboard defaults
working-directory: submodules/TelegramUI/Components/RichTextEditor/Host/Engine/Scripts/tests
run: >-
python3 -m unittest
test_ime_keyboard_contract.IMEKeyboardContractTests.test_defaults_inventory_parser_rejects_malformed_output
test_ime_keyboard_contract.IMEKeyboardContractTests.test_defaults_inventory_parser_handles_escapes_and_rejects_long_invalid_strings
codecs:
runs-on: macos-26
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Native codec test dependencies
run: |
if ! pkg-config --exists libavcodec libavformat libavutil libswscale; then
brew install pkg-config ffmpeg
fi
- name: OpenH264 and Subcodec round-trip regression suites
run: python3 Tests/CodeScanning/native_regressions.py
watch-codecs:
runs-on: macos-26
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
package: [OpusKit, WebPKit, RLottieKit]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Package codec regression tests
run: swift test --package-path Telegram/WatchApp/Packages/${{ matrix.package }}
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ OPUS_CUSTOM_NOSTATIC int opus_custom_encoder_get_size(const CELTMode *mode, int
int size = sizeof(struct CELTEncoder)
+ (channels*mode->overlap-1)*sizeof(celt_sig) /* celt_sig in_mem[channels*mode->overlap]; */
+ channels*COMBFILTER_MAXPERIOD*sizeof(celt_sig) /* celt_sig prefilter_mem[channels*COMBFILTER_MAXPERIOD]; */
+ 4*channels*mode->nbEBands*sizeof(opus_val16); /* opus_val16 oldBandE[channels*mode->nbEBands]; */
+ (unsigned long)(4)*channels*mode->nbEBands*sizeof(opus_val16); /* opus_val16 oldBandE[channels*mode->nbEBands]; */
/* opus_val16 oldLogE[channels*mode->nbEBands]; */
/* opus_val16 oldLogE2[channels*mode->nbEBands]; */
/* opus_val16 energyError[channels*mode->nbEBands]; */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ int main(int argc, char *argv[])
opus_custom_encoder_ctl(enc,OPUS_SET_COMPLEXITY(complexity));
}

in = (opus_int16*)malloc(frame_size*channels*sizeof(opus_int16));
out = (opus_int16*)malloc(frame_size*channels*sizeof(opus_int16));
in = (opus_int16*)malloc((unsigned long)(frame_size)*channels*sizeof(opus_int16));
out = (opus_int16*)malloc((unsigned long)(frame_size)*channels*sizeof(opus_int16));

while (!feof(fin))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ void testdiv(void)
#ifdef FIXED_POINT
prod = (1./32768./65526.)*val*i;
#else
prod = val*i;
prod = (double)(val)*i;
#endif
if (fabs(prod-1) > .00025)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ void silk_CNG(
}
}
/* Update CNG excitation buffer with excitation from this subframe */
silk_memmove( &psCNG->CNG_exc_buf_Q14[ psDec->subfr_length ], psCNG->CNG_exc_buf_Q14, ( psDec->nb_subfr - 1 ) * psDec->subfr_length * sizeof( opus_int32 ) );
silk_memmove( &psCNG->CNG_exc_buf_Q14[ psDec->subfr_length ], psCNG->CNG_exc_buf_Q14, (unsigned long)(( psDec->nb_subfr - 1 )) * psDec->subfr_length * sizeof( opus_int32 ) );
silk_memcpy( psCNG->CNG_exc_buf_Q14, &psDec->exc_Q14[ subfr * psDec->subfr_length ], psDec->subfr_length * sizeof( opus_int32 ) );

/* Smooth gains */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ silk_float silk_burg_modified_FLP( /* O returns residual energy
tmp1 = x_ptr[ n ];
tmp2 = x_ptr[ subfr_length - n - 1 ];
for( k = 0; k < n; k++ ) {
C_first_row[ k ] -= x_ptr[ n ] * x_ptr[ n - k - 1 ];
C_last_row[ k ] -= x_ptr[ subfr_length - n - 1 ] * x_ptr[ subfr_length - n + k ];
C_first_row[ k ] -= (double)(x_ptr[ n ]) * x_ptr[ n - k - 1 ];
C_last_row[ k ] -= (double)(x_ptr[ subfr_length - n - 1 ]) * x_ptr[ subfr_length - n + k ];
Atmp = Af[ k ];
tmp1 += x_ptr[ n - k - 1 ] * Atmp;
tmp2 += x_ptr[ subfr_length - n + k ] * Atmp;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,12 @@ void silk_residual_energy_FLP(

/* Filter input to create the LPC residual for each frame half, and measure subframe energies */
silk_LPC_analysis_filter_FLP( LPC_res, a[ 0 ], x + 0 * shift, 2 * shift, LPC_order );
nrgs[ 0 ] = ( silk_float )( gains[ 0 ] * gains[ 0 ] * silk_energy_FLP( LPC_res_ptr + 0 * shift, subfr_length ) );
nrgs[ 1 ] = ( silk_float )( gains[ 1 ] * gains[ 1 ] * silk_energy_FLP( LPC_res_ptr + 1 * shift, subfr_length ) );
nrgs[ 0 ] = ( silk_float )( (double)(gains[ 0 ]) * gains[ 0 ] * silk_energy_FLP( LPC_res_ptr + 0 * shift, subfr_length ) );
nrgs[ 1 ] = ( silk_float )( (double)(gains[ 1 ]) * gains[ 1 ] * silk_energy_FLP( LPC_res_ptr + 1 * shift, subfr_length ) );

if( nb_subfr == MAX_NB_SUBFR ) {
silk_LPC_analysis_filter_FLP( LPC_res, a[ 1 ], x + 2 * shift, 2 * shift, LPC_order );
nrgs[ 2 ] = ( silk_float )( gains[ 2 ] * gains[ 2 ] * silk_energy_FLP( LPC_res_ptr + 0 * shift, subfr_length ) );
nrgs[ 3 ] = ( silk_float )( gains[ 3 ] * gains[ 3 ] * silk_energy_FLP( LPC_res_ptr + 1 * shift, subfr_length ) );
nrgs[ 2 ] = ( silk_float )( (double)(gains[ 2 ]) * gains[ 2 ] * silk_energy_FLP( LPC_res_ptr + 0 * shift, subfr_length ) );
nrgs[ 3 ] = ( silk_float )( (double)(gains[ 3 ]) * gains[ 3 ] * silk_energy_FLP( LPC_res_ptr + 1 * shift, subfr_length ) );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ opus_int32 mapping_matrix_get_size(int rows, int cols)
*/
if (rows > 255 || cols > 255)
return 0;
size = rows * (opus_int32)cols * sizeof(opus_int16);
size = (unsigned long)(rows) * (opus_int32)cols * sizeof(opus_int16);
if (size > 65004)
return 0;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ static void band_energy(float *_out,float *_ps,const int *_bands,int _nbands,
size_t xi;
int xj;
int ps_sz;
window=(float *)opus_malloc((3+_nchannels)*_window_sz*sizeof(*window));
window=(float *)opus_malloc((unsigned long)((3+_nchannels))*_window_sz*sizeof(*window));
c=window+_window_sz;
s=c+_window_sz;
x=s+_window_sz;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ int opus_projection_decoder_init(OpusProjectionDecoder *st, opus_int32 Fs,

/* Verify supplied matrix size. */
nb_input_streams = streams + coupled_streams;
expected_matrix_size = nb_input_streams * channels * sizeof(opus_int16);
expected_matrix_size = (unsigned long)(nb_input_streams) * channels * sizeof(opus_int16);
if (expected_matrix_size != demixing_matrix_size)
{
RESTORE_STACK;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ int opus_projection_encoder_ctl(OpusProjectionEncoder *st, int request, ...)
goto bad_arg;
}
*value =
ms_encoder->layout.nb_channels * (ms_encoder->layout.nb_streams
(unsigned long)(ms_encoder->layout.nb_channels) * (ms_encoder->layout.nb_streams
+ ms_encoder->layout.nb_coupled_streams) * sizeof(opus_int16);
}
break;
Expand Down Expand Up @@ -476,7 +476,7 @@ int opus_projection_encoder_ctl(OpusProjectionEncoder *st, int request, ...)
goto bad_arg;
}
internal_short = mapping_matrix_get_data(demixing_matrix);
internal_size = nb_input_streams * nb_output_streams * sizeof(opus_int16);
internal_size = (unsigned long)(nb_input_streams) * nb_output_streams * sizeof(opus_int16);
if (external_size != internal_size)
{
goto bad_arg;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1583,7 +1583,7 @@ static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int r
if (req_comp == img_n) return data;
STBI_ASSERT(req_comp >= 1 && req_comp <= 4);

good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2);
good = (stbi__uint16 *) stbi__malloc((size_t)(req_comp) * x * y * 2);
if (good == NULL) {
STBI_FREE(data);
return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ void VBitmap::Impl::reset(size_t width, size_t height, VBitmap::Format format)
mDepth = depth(format);
mStride = ((mWidth * mDepth + 31) >> 5)
<< 2; // bytes per scanline (must be multiple of 4)
mOwnData = std::make_unique<uchar[]>(mStride * mHeight);
mOwnData = std::make_unique<uchar[]>((size_t)(mStride) * mHeight);
}

void VBitmap::Impl::reset(uchar *data, size_t width, size_t height, size_t bytesPerLine,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class __attribute__((visibility("default"))) RlottieWasm {
mWidth = width;
mHeight = height;

mBuffer = std::make_unique<uint8_t[]>(mWidth * mHeight * 4);
mBuffer = std::make_unique<uint8_t[]>((size_t)(mWidth) * mHeight * 4);
}

explicit RlottieWasm(const char *data)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ WEBP_NODISCARD static int ALPHInit(ALPHDecoder* const dec, const uint8_t* data,
// No need to copy the scaling parameters.

if (dec->method_ == ALPHA_NO_COMPRESSION) {
const size_t alpha_decoded_size = dec->width_ * dec->height_;
const size_t alpha_decoded_size = (size_t)(dec->width_) * dec->height_;
ok = (alpha_data_size >= alpha_decoded_size);
} else {
assert(dec->method_ == ALPHA_LOSSLESS_COMPRESSION);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -688,11 +688,11 @@ static int AllocateMemory(VP8Decoder* const dec) {
const size_t mb_info_size = (mb_w + 1) * sizeof(VP8MB);
const size_t f_info_size =
(dec->filter_type_ > 0) ?
mb_w * (dec->mt_method_ > 0 ? 2 : 1) * sizeof(VP8FInfo)
(unsigned long)(mb_w) * (dec->mt_method_ > 0 ? 2 : 1) * sizeof(VP8FInfo)
: 0;
const size_t yuv_size = YUV_SIZE * sizeof(*dec->yuv_b_);
const size_t mb_data_size =
(dec->mt_method_ == 2 ? 2 : 1) * mb_w * sizeof(*dec->mb_data_);
(unsigned long)((dec->mt_method_ == 2 ? 2 : 1)) * mb_w * sizeof(*dec->mb_data_);
const size_t cache_height = (16 * num_caches
+ kFilterExtraRows[dec->filter_type_]) * 3 / 2;
const size_t cache_size = top_size * cache_height;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ static WEBP_INLINE void DoHorizontalFilter_C(const uint8_t* in,
int row, int num_rows,
int inverse, uint8_t* out) {
const uint8_t* preds;
const size_t start_offset = row * stride;
const size_t start_offset = (size_t)(row) * stride;
const int last_row = row + num_rows;
DCHECK(in, out);
in += start_offset;
Expand Down Expand Up @@ -86,7 +86,7 @@ static WEBP_INLINE void DoVerticalFilter_C(const uint8_t* in,
int row, int num_rows,
int inverse, uint8_t* out) {
const uint8_t* preds;
const size_t start_offset = row * stride;
const size_t start_offset = (size_t)(row) * stride;
const int last_row = row + num_rows;
DCHECK(in, out);
in += start_offset;
Expand Down Expand Up @@ -131,7 +131,7 @@ static WEBP_INLINE void DoGradientFilter_C(const uint8_t* in,
int row, int num_rows,
int inverse, uint8_t* out) {
const uint8_t* preds;
const size_t start_offset = row * stride;
const size_t start_offset = (size_t)(row) * stride;
const int last_row = row + num_rows;
DCHECK(in, out);
in += start_offset;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ static WEBP_INLINE void DoHorizontalFilter_SSE2(const uint8_t* in,
int stride,
int row, int num_rows,
uint8_t* out) {
const size_t start_offset = row * stride;
const size_t start_offset = (size_t)(row) * stride;
const int last_row = row + num_rows;
DCHECK(in, out);
in += start_offset;
Expand Down Expand Up @@ -111,7 +111,7 @@ static WEBP_INLINE void DoVerticalFilter_SSE2(const uint8_t* in,
int width, int height, int stride,
int row, int num_rows,
uint8_t* out) {
const size_t start_offset = row * stride;
const size_t start_offset = (size_t)(row) * stride;
const int last_row = row + num_rows;
DCHECK(in, out);
in += start_offset;
Expand Down Expand Up @@ -174,7 +174,7 @@ static WEBP_INLINE void DoGradientFilter_SSE2(const uint8_t* in,
int width, int height, int stride,
int row, int num_rows,
uint8_t* out) {
const size_t start_offset = row * stride;
const size_t start_offset = (size_t)(row) * stride;
const int last_row = row + num_rows;
DCHECK(in, out);
in += start_offset;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ static int PaletteSortModifiedZeng(const WebPPicture* const pic,
if (num_colors <= 1) return 1;
// Build the co-occurrence matrix.
cooccurrence =
(uint32_t*)WebPSafeCalloc(num_colors * num_colors, sizeof(*cooccurrence));
(uint32_t*)WebPSafeCalloc((uint64_t)(num_colors) * num_colors, sizeof(*cooccurrence));
if (cooccurrence == NULL) {
return 0;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ static int InitParams(uint8_t* const data, int width, int height, int stride,
int radius, SmoothParams* const p) {
const int R = 2 * radius + 1; // total size of the kernel

const size_t size_scratch_m = (R + 1) * width * sizeof(*p->start_);
const size_t size_scratch_m = (unsigned long)((R + 1)) * width * sizeof(*p->start_);
const size_t size_m = width * sizeof(*p->average_);
const size_t size_lut = (1 + 2 * LUT_SIZE) * sizeof(*p->correction_);
const size_t total_size = size_scratch_m + size_m + size_lut;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ int QuantizeLevels(uint8_t* const data, int width, int height,
int q_level[NUM_SYMBOLS] = { 0 };
double inv_q_level[NUM_SYMBOLS] = { 0 };
int min_s = 255, max_s = 0;
const size_t data_size = height * width;
const size_t data_size = (size_t)(height) * width;
int i, num_levels_in, iter;
double last_err = 1.e38, err = 0.;
const double err_threshold = ERROR_THRESHOLD * data_size;
Expand Down
34 changes: 34 additions & 0 deletions Tests/CodeScanning/native_regressions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""Run the existing codec round-trip suites on an Apple Silicon host."""
import pathlib
import re
import subprocess
import tempfile


ROOT = pathlib.Path(__file__).resolve().parents[2]
SOURCE = ROOT / 'third-party' / 'subcodec'


def run(*command):
subprocess.run(command, check=True, cwd=ROOT)


def main():
cmake = (SOURCE / 'CMakeLists.txt').read_text()
targets = []
for name in ('CORE_TESTS', 'E2E_TESTS'):
match = re.search(r'set\(' + name + r'\s+([^)]*)\)', cmake)
if match is None:
raise RuntimeError('Missing codec test target list: ' + name)
targets.extend(match.group(1).split())
if not targets or len(targets) != len(set(targets)):
raise RuntimeError('The codec test target list must be nonempty and unique')
with tempfile.TemporaryDirectory(prefix='telegram-codec-regressions-') as directory:
run('cmake', '-S', str(SOURCE), '-B', directory, '-DCMAKE_BUILD_TYPE=Release')
run('cmake', '--build', directory, '--parallel', '3', '--target', *targets)
# Several existing tests use fixed /tmp fixture names; keep execution serial.
run('ctest', '--test-dir', directory, '--output-on-failure', '--parallel', '1')


if __name__ == '__main__':
main()
33 changes: 33 additions & 0 deletions docs/code-scanning-remediation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Code scanning remediation — October 2026

## Source changes

Integer products are evaluated in the destination width before allocation-size, stride, offset and accumulator conversions. Floating-point products accumulated in double are evaluated in double except the intentionally float-rounded FFmpeg dither paths. Macro allocation helpers promote their operands before multiplying.

The keyboard-defaults parser uses disjoint escaped/unescaped character alternatives, avoiding exponential backtracking on malformed backslash-heavy strings. A bounded subprocess regression verifies rejection of a 100,000-backslash unterminated string.

## Verification

Local checks completed: FFmpeg and JPEG XL core library builds; libwebp build and three lossless encode/decode pixel comparisons; Clang compilation checks for all changed Watch Opus/WebP codec source units and the OpenSubdiv/CompanionRenderer units.

Apple Silicon Subcodec and Watch Swift-package regression suites run in `.github/workflows/security-regressions.yml`. Full iOS integration is verified by the existing application build before release. The WebAssembly example is not an iOS build target and cannot be executed by the native host checks.

## Reviewed findings

These findings are documented individually in the GitHub Code scanning dashboard; scanning rules remain enabled.

### Alerts 1269, 1270, 1271, 1272, 1273, 1274, 1275, 1276, 1277, 1278, 1279, 1280, 1281, 1282, 1283, 1284, 1285, 1286, 1287, 1288

Reviewed the H.264 bit-depth templates: block is declared int16_t* for the shared interface, while high-bit-depth instantiations store dctcoef as 32-bit coefficients. Multiplying the coefficient offset by sizeof(pixel) (1 for 8-bit, 2 for high-bit-depth) intentionally converts to int16_t storage units. The loop indices are bounded. Removing this scaling would address the wrong coefficient block and corrupt decoding; this is not double-scaling a pointer of the actual stored coefficient type.

### Alerts 224

Reviewed every ParseOffset caller in time_zone_posix.cc: hours are constrained to 0..24 or -167..167, minutes/seconds to 0..59, and sign is +/-1. All intermediate products and the final offset fit signed 32-bit int (absolute value <=604799). The later int_fast32_t conversion cannot expose an overflowing multiplication on any supported ABI.

### Alerts 1236, 1237

Reviewed the floating-point dither template: the products are float operations intentionally accumulated in double, using normalized audio samples, fixed scale and bounded filter/noise-shaping state. They are not integer buffer-size calculations. Promoting these operands changes the specified float rounding of the audio dither path; no pointer, allocation length or security boundary is derived from the product.

### Alerts 4

Reviewed DecryptMatch.py: this is a read-only decoder for existing Fastlane Match encrypted certificate archives. EVP_BytesToKey reproduces the legacy OpenSSL V1 MD5/SHA256 derivation required by that file format; it is not a password verifier or a password-hash database and does not create new encrypted archives. Changing the digest would make existing archives undecryptable. The versioned newer archive decoder uses its own prescribed KDF.
Loading
Loading