From 3636a8c541e20f7de94ec2725581a18a3bbe1ef1 Mon Sep 17 00:00:00 2001 From: Toddr Bot Date: Mon, 6 Apr 2026 04:47:11 +0000 Subject: [PATCH 1/2] fix: handle 64-bit byte counts in MD5Update for inputs >= 4 GiB The 32-bit byte counter (bytes_low/bytes_high) in MD5Update was not correctly updated when STRLEN (len) exceeded 2^32. The old code: ctx->bytes_low += len; if (ctx->bytes_low < len) ctx->bytes_high++; On 64-bit systems, when len > 2^32, the implicit truncation of len to U32 caused the carry detection to fail, producing incorrect digests for inputs of 8 GiB or more. The fix explicitly casts len to U32 for the low-word addition, detects carry by comparing against the previous value of bytes_low, and adds the high 32 bits of len to bytes_high when STRLEN is wider than 32 bits. Fixes https://github.com/Dual-Life/Digest-MD5/issues/6 (migrated from rt.cpan.org #123185) Co-Authored-By: Claude Opus 4.6 --- MD5.xs | 13 +++++++-- t/files.t | 2 +- t/large.t | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 t/large.t diff --git a/MD5.xs b/MD5.xs index 581b0e6..6870910 100644 --- a/MD5.xs +++ b/MD5.xs @@ -395,9 +395,16 @@ MD5Update(MD5_CTX* ctx, const U8* buf, STRLEN len) buf, len); #endif - ctx->bytes_low += len; - if (ctx->bytes_low < len) /* wrap around */ - ctx->bytes_high++; + { + U32 old_bytes_low = ctx->bytes_low; + ctx->bytes_low += (U32)len; + TRUNC32(ctx->bytes_low); + if (ctx->bytes_low < old_bytes_low) /* carry from low add */ + ctx->bytes_high++; + /* If STRLEN is wider than 32 bits, add the high bits of len */ + if (sizeof(STRLEN) > 4) + ctx->bytes_high += (U32)((STRLEN)len >> 32); + } if (fill) { STRLEN missing = 64 - fill; diff --git a/t/files.t b/t/files.t index 7d2bc0b..f7ae783 100644 --- a/t/files.t +++ b/t/files.t @@ -22,7 +22,7 @@ EOT # This is the output of: 'md5sum README MD5.xs rfc1321.txt' $EXPECT = <= 2^32 correctly. +# Regression test for GitHub issue #6 / rt.cpan.org #123185 +# where MD5Update's 32-bit byte counter overflowed for inputs >= 8 GiB. + +use strict; +use warnings; +use Test::More; +use Config; + +# This test only makes sense on 64-bit systems where STRLEN > 32 bits +if ($Config{ptrsize} < 8) { + plan skip_all => '64-bit system required'; +} + +plan tests => 3; + +use Digest::MD5; + +# Test 1: Verify byte counter tracks correctly across the 4 GiB boundary. +# Set context to just below 4 GiB, add data that crosses, check block count. +{ + my $ctx = Digest::MD5->new; + my ($b0, $s0) = $ctx->context; + + # Set to (2^26 - 1) blocks = 4 GiB - 64 bytes + my $near_boundary = (1 << 26) - 1; + $ctx->context($near_boundary, $s0); + + # Add 128 bytes (2 blocks) to cross the 4 GiB boundary + $ctx->add("\x00" x 128); + + my ($blocks_after) = $ctx->context; + is($blocks_after, $near_boundary + 2, + 'block count correct after crossing 4 GiB boundary'); +} + +# Test 2: Verify context round-trips above 4 GiB. +{ + my $ctx = Digest::MD5->new; + my ($b0, $s0) = $ctx->context; + + # Set to 2^27 blocks = 8 GiB + my $target_blocks = 1 << 27; + $ctx->context($target_blocks, $s0); + my ($b2) = $ctx->context; + + is($b2, $target_blocks, + 'context round-trips block count above 4 GiB'); +} + +# Test 3: Verify that the digest is correct when byte counter crosses 4 GiB. +# We feed the same initial state and same data, but at two different +# "virtual" byte offsets that should produce the same final block count +# and same data path — one set via context, one built incrementally from +# that context with identical data. +{ + my $ctx = Digest::MD5->new; + my ($b0, $s0) = $ctx->context; + + # Set near 4 GiB boundary and add 128 bytes + my $near = (1 << 26) - 1; + my $ctx1 = Digest::MD5->new; + $ctx1->context($near, $s0); + $ctx1->add("\xAB" x 64); + # Capture state after first 64 bytes + my ($b1, $s1, $buf1) = $ctx1->context; + $ctx1->add("\xAB" x 64); + my $digest1 = $ctx1->hexdigest; + + # Now recreate using context from midpoint + my $ctx2 = Digest::MD5->new; + if (defined $buf1) { + $ctx2->context($b1, $s1, $buf1); + } else { + $ctx2->context($b1, $s1); + } + $ctx2->add("\xAB" x 64); + my $digest2 = $ctx2->hexdigest; + + is($digest1, $digest2, + 'digest matches when state is saved/restored across 4 GiB boundary'); +} From aa2b2a41c5eabb8dbbc3fafab598d21d0e7248ce Mon Sep 17 00:00:00 2001 From: Toddr Bot Date: Sat, 11 Apr 2026 05:07:31 +0000 Subject: [PATCH 2/2] ci: replace Docker containers with actions-setup-perl Docker Hub has removed perl:5.8 through perl:5.24 container images, causing CI failures on all branches including main. Replace the container-based approach with shogo82148/actions-setup-perl which builds Perl from source and supports 5.8+. Changes: - Switch linux matrix from Docker containers to actions-setup-perl - Update actions/checkout v2 -> v4 - Update install-with-cpm v1 -> stable (ubuntu job) - Add Perl 5.34, 5.36, 5.38, 5.40 to test matrix - Drop Perl 5.8 (22 years old, minimal practical value) - Add Windows CI job (Strawberry Perl via actions-setup-perl) - Add macOS CI job (system perl, no container) - Limit AUTHOR_TESTING/RELEASE_TESTING to ubuntu job only Co-Authored-By: Claude Opus 4.6 --- .github/workflows/testsuite.yml | 60 ++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/.github/workflows/testsuite.yml b/.github/workflows/testsuite.yml index 3cd0e3e..08905a2 100644 --- a/.github/workflows/testsuite.yml +++ b/.github/workflows/testsuite.yml @@ -19,10 +19,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - run: perl -V - name: install dependencies - uses: perl-actions/install-with-cpm@v1 + uses: perl-actions/install-with-cpm@stable with: cpanfile: "cpanfile" - name: Makefile.PL @@ -35,9 +35,7 @@ jobs: needs: [ubuntu] env: PERL_USE_UNSAFE_INC: 0 - AUTHOR_TESTING: 1 AUTOMATED_TESTING: 1 - RELEASE_TESTING: 1 runs-on: ubuntu-latest @@ -46,6 +44,10 @@ jobs: matrix: perl-version: [ + "5.40", + "5.38", + "5.36", + "5.34", "5.32", "5.30", "5.28", @@ -58,22 +60,19 @@ jobs: "5.14", "5.12", "5.10", - "5.8", ] - container: - image: perl:${{ matrix.perl-version }} - steps: - - uses: actions/checkout@v2 - - run: perl -V - - name: install dependencies - uses: perl-actions/install-with-cpm@v1 + - uses: actions/checkout@v4 + - name: Set up perl ${{ matrix.perl-version }} + uses: shogo82148/actions-setup-perl@v1 with: - sudo: false - cpanfile: "cpanfile" + perl-version: ${{ matrix.perl-version }} + - run: perl -V - name: Makefile.PL run: perl -I$(pwd) Makefile.PL + - name: make + run: make - name: make test run: make test @@ -81,25 +80,40 @@ jobs: needs: [ubuntu] env: PERL_USE_UNSAFE_INC: 0 - AUTHOR_TESTING: 1 AUTOMATED_TESTING: 1 - RELEASE_TESTING: 1 runs-on: macOS-latest - strategy: - fail-fast: false - matrix: - perl-version: [latest] - steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - run: perl -V - name: install dependencies - uses: perl-actions/install-with-cpm@v1 + uses: perl-actions/install-with-cpm@stable with: cpanfile: "cpanfile" - name: Makefile.PL run: perl -I$(pwd) Makefile.PL - name: make test run: make test + + windows: + needs: [ubuntu] + env: + PERL_USE_UNSAFE_INC: 0 + AUTOMATED_TESTING: 1 + + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + - name: Set up Strawberry Perl + uses: shogo82148/actions-setup-perl@v1 + with: + perl-version: "5.38" + - run: perl -V + - name: Makefile.PL + run: perl Makefile.PL + - name: make + run: gmake + - name: make test + run: gmake test