diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 24dda549f..92056856e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,7 +31,7 @@ jobs: LD: ${{ matrix.cc }} steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Ruby version @@ -46,13 +46,13 @@ jobs: timeout-minutes: 15 steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Ruby version run: ruby -v - name: Cache cosmocc - uses: actions/cache@v5 + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 id: cache-cosmocc with: path: ~/cosmo @@ -74,7 +74,7 @@ jobs: MRUBY_CONFIG: ci/msvc steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Ruby version diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 3ba297119..28db35af5 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -18,7 +18,7 @@ jobs: language: ["actions"] steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Initialize CodeQL diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 937e7a1ef..22302ce1d 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -19,7 +19,7 @@ jobs: LDFLAGS: --coverage steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Ruby version @@ -39,7 +39,7 @@ jobs: echo \`\`\` } > "$GITHUB_STEP_SUMMARY" - name: Upload coverage report - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: coverage-${{ github.sha }} path: coverage/ diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 0e2e4e653..e2ea661c7 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -9,7 +9,7 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: - - uses: actions/labeler@v6 + - uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" sync-labels: true diff --git a/.github/workflows/ls-lint.yml b/.github/workflows/ls-lint.yml index bce70fd5b..86644ba90 100644 --- a/.github/workflows/ls-lint.yml +++ b/.github/workflows/ls-lint.yml @@ -11,9 +11,9 @@ jobs: name: Run ls-lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - uses: ls-lint/action@v2.3.1 + - uses: ls-lint/action@02e380fe8733d499cbfc9e22276de5085508a5bd # v2.3.1 with: config: .github/linters/.ls-lint.yml diff --git a/.github/workflows/oss-fuzz.yml b/.github/workflows/oss-fuzz.yml index 6a8ded6ae..26fade42c 100644 --- a/.github/workflows/oss-fuzz.yml +++ b/.github/workflows/oss-fuzz.yml @@ -20,7 +20,7 @@ jobs: fuzz-seconds: 600 dry-run: false - name: Upload Crash - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: failure() with: name: artifacts diff --git a/.github/workflows/pre-commit-manual.yml b/.github/workflows/pre-commit-manual.yml index 2c0f321e7..9857e9703 100644 --- a/.github/workflows/pre-commit-manual.yml +++ b/.github/workflows/pre-commit-manual.yml @@ -12,10 +12,10 @@ jobs: runs-on: ubuntu-latest steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - uses: j178/prek-action@v2 + - uses: j178/prek-action@53276d8b0d10f8b6672aa85b4588c6921d0370cc # v2.0.1 with: install-only: true - name: Run manual pre-commit hooks diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 3e516d4a0..7e01bd0c7 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -12,9 +12,9 @@ jobs: runs-on: ubuntu-latest steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - uses: j178/prek-action@v2 + - uses: j178/prek-action@53276d8b0d10f8b6672aa85b4588c6921d0370cc # v2.0.1 with: extra-args: --all-files diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cf849c6df..26403803f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,7 @@ jobs: fail-fast: false steps: - name: "Checkout ${{ github.ref_name }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Builds diff --git a/.github/workflows/super-linter.yml b/.github/workflows/super-linter.yml index c26bb56f2..3dd83ddbf 100644 --- a/.github/workflows/super-linter.yml +++ b/.github/workflows/super-linter.yml @@ -15,13 +15,13 @@ jobs: runs-on: ubuntu-latest steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: # Full git history is needed to get a proper list of changed files within `super-linter` fetch-depth: 0 persist-credentials: false - name: Lint Code Base - uses: super-linter/super-linter/slim@v8.5.0 + uses: super-linter/super-linter/slim@9e863354e3ff62e0727d37183162c4a88873df41 # v8.6.0 env: # VALIDATE_BASH_EXEC: true VALIDATE_DOCKERFILE_HADOLINT: true diff --git a/NEWS.md b/NEWS.md index a05463e7e..200b58b9e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -23,6 +23,8 @@ mruby now supports pattern matching (case/in) syntax: - Trailing comma in method definition parameters: `def foo(a, b,)` ([f78334b](https://github.com/mruby/mruby/commit/f78334b)) - Array/Hash/String subclasses can now override `[]` and `[]=` methods ([#6675](https://github.com/mruby/mruby/pull/6675)) - `OP_SETIDX` optimization for Array and Hash ([ddd8fe1](https://github.com/mruby/mruby/commit/ddd8fe1)) +- `case`/`in` without `else` now raises `NoMatchingPatternError` ([d8de35b](https://github.com/mruby/mruby/commit/d8de35b)) +- Allow compound statement in parenthesized argument context ([919cbd8](https://github.com/mruby/mruby/commit/919cbd8)) # Changes in C API @@ -37,6 +39,8 @@ mruby now supports pattern matching (case/in) syntax: - `mrb_open()` returns mrb_state with exc set on init failure ([05ffe0c](https://github.com/mruby/mruby/commit/05ffe0c)) - `mrb_utf8_to_buf()` for UTF-8 encoding consolidation ([7e28e68](https://github.com/mruby/mruby/commit/7e28e68)) - `kh_is_end()` macro for safe khash iteration ([893cc75](https://github.com/mruby/mruby/commit/893cc75)) +- `mrb_bigint_p()` always defined regardless of bigint gem presence ([6c4a8c0](https://github.com/mruby/mruby/commit/6c4a8c0)) +- `RInteger` and `RFloat` added to `RVALUE` union ([13dbca0](https://github.com/mruby/mruby/commit/13dbca0)) # ROM Method Tables @@ -76,6 +80,7 @@ mruby-symbol-ext, mruby-range-ext, mruby-object-ext. - Emscripten: use native WASM exception handling ([ca364e3](https://github.com/mruby/mruby/commit/ca364e3)) - HAL (Hardware Abstraction Layer) for platform abstraction in mruby-io, mruby-socket, mruby-dir, mruby-task ([74ca22f](https://github.com/mruby/mruby/commit/74ca22f)) - `MRUBY_MIRB_READLINE` environment variable to control readline library selection ([0aafb83](https://github.com/mruby/mruby/commit/0aafb83)) +- MSYS2 drive letter support in build script ([77f6ffe](https://github.com/mruby/mruby/commit/77f6ffe)) - Inter-gem headers separated from external API headers ([#6671](https://github.com/mruby/mruby/pull/6671)) # Changes in mrbgems @@ -110,6 +115,7 @@ mruby-symbol-ext, mruby-range-ext, mruby-object-ext. ## Other Gem Changes - **_NOTE_**: `Hash#deconstruct_keys` removed for CRuby compatibility ([34b9412](https://github.com/mruby/mruby/commit/34b9412)) +- **mruby-enum-lazy**: Fix `Lazy#flat_map` to handle non-enumerable block return values ([#6765](https://github.com/mruby/mruby/pull/6765)) - **mruby-array-ext**: Add `Array#find` and `Array#rfind` methods - **mruby-io**: Add `IO#putc` and `Kernel#putc` ([baff6e6](https://github.com/mruby/mruby/commit/baff6e6)) - **mruby-random**: Replace xoshiro with PCG for better memory efficiency ([f1bab01](https://github.com/mruby/mruby/commit/f1bab01)) @@ -125,6 +131,8 @@ mruby-symbol-ext, mruby-range-ext, mruby-object-ext. - Optimized masgn to generate literals directly into target registers ([fb5d966](https://github.com/mruby/mruby/commit/fb5d966)) - Optimized splat of literal arrays in args/literals ([1cb8d73](https://github.com/mruby/mruby/commit/1cb8d73)) - Early termination after too many parse errors ([510ebd7](https://github.com/mruby/mruby/commit/510ebd7)) +- Chunk array literals at 64 elements to reduce register pressure ([f98d641](https://github.com/mruby/mruby/commit/f98d641)) +- Chunk `%w()` and `%i()` literals to reduce register pressure ([62cf0dc](https://github.com/mruby/mruby/commit/62cf0dc)) # VM Optimizations @@ -185,6 +193,10 @@ Other optimizations: - [#6705](https://github.com/mruby/mruby/issues/6705) Can't get outer class of an object in C - [#6713](https://github.com/mruby/mruby/issues/6713) mruby-polarssl not work - [#6720](https://github.com/mruby/mruby/issues/6720) Random float range: different behavior from CRuby +- [#6722](https://github.com/mruby/mruby/issues/6722) RBreak size overflow on 32-bit platforms with MRB_NO_BOXING +- [#6740](https://github.com/mruby/mruby/issues/6740) `%w()`/`%i()` register pressure with large literals +- [#6741](https://github.com/mruby/mruby/issues/6741) `case`/`in` without `else` should raise `NoMatchingPatternError` +- [#6760](https://github.com/mruby/mruby/issues/6760) `mrb_gc_unregister()` not removing all matching entries # Merged Pull Requests @@ -260,6 +272,7 @@ Other optimizations: - [#6589](https://github.com/mruby/mruby/pull/6589) Add pre-commit hook `check-zip-file-is-not-committed` - [#6591](https://github.com/mruby/mruby/pull/6591) mruby-eval fix license link in README - [#6593](https://github.com/mruby/mruby/pull/6593) README: Add Contributors Avatars, Star History, Table of Contents +- [#6598](https://github.com/mruby/mruby/pull/6598) Fix heap buffer overflow in `#method_missing` - [#6599](https://github.com/mruby/mruby/pull/6599) pre-commit: run `markdown-link-check`, `oxipng`, `prettier` manually - [#6600](https://github.com/mruby/mruby/pull/6600) `dreamcast_shelf build config`: update to use KallistiOS wrappers - [#6601](https://github.com/mruby/mruby/pull/6601) fix: skip local build_config.rb when working in MRUBY_ROOT @@ -303,6 +316,33 @@ Other optimizations: - [#6716](https://github.com/mruby/mruby/pull/6716) Fixes identity for proc object - [#6717](https://github.com/mruby/mruby/pull/6717) Fix mruby-task: wrapping by critical section and setting initial task receiver - [#6718](https://github.com/mruby/mruby/pull/6718) Add installation instructions for conda and Homebrew +- [#6723](https://github.com/mruby/mruby/pull/6723) Add `RInteger` and `RFloat` to `RVALUE` +- [#6727](https://github.com/mruby/mruby/pull/6727) Language documentation: update wording of "overloading" section +- [#6729](https://github.com/mruby/mruby/pull/6729) Simplifying dependency addition for gensym task +- [#6730](https://github.com/mruby/mruby/pull/6730) Simplifying presym file generation actions +- [#6733](https://github.com/mruby/mruby/pull/6733) Include `mruby/presym.h` for all source files +- [#6734](https://github.com/mruby/mruby/pull/6734) Chunk array literals at 64 elements to reduce register pressure +- [#6735](https://github.com/mruby/mruby/pull/6735) Prevent full recompilation without changes to presym file +- [#6739](https://github.com/mruby/mruby/pull/6739) Fix MSYS2 build error with drive letters +- [#6743](https://github.com/mruby/mruby/pull/6743) Chunk `%w()` and `%i()` literals to reduce register pressure +- [#6744](https://github.com/mruby/mruby/pull/6744) Raise `NoMatchingPatternError` in `case`/`in` without `else` +- [#6747](https://github.com/mruby/mruby/pull/6747) Correctly handle empty hash as default named argument +- [#6749](https://github.com/mruby/mruby/pull/6749) Fix microcontroller profile +- [#6750](https://github.com/mruby/mruby/pull/6750) Fix out-of-bounds read and divide-by-zero in `Array#product` +- [#6752](https://github.com/mruby/mruby/pull/6752) Fix `attr_reader`-generated methods accepting extra arguments +- [#6753](https://github.com/mruby/mruby/pull/6753) Further optimize `Array#product` +- [#6754](https://github.com/mruby/mruby/pull/6754) Mark `attr_reader` procs as noarg +- [#6755](https://github.com/mruby/mruby/pull/6755) Reload `ci` after `mrb_hash_delete_key()` in keyword argument handling +- [#6756](https://github.com/mruby/mruby/pull/6756) Avoid impact of object modifications caused by `mrb_vm_exec()` calls +- [#6758](https://github.com/mruby/mruby/pull/6758) Don't assign result of `mrb_funcall()` directly to `regs` +- [#6759](https://github.com/mruby/mruby/pull/6759) Define `mrb_bigint_p()` always +- [#6761](https://github.com/mruby/mruby/pull/6761) Fix `mrb_gc_unregister()` to remove all matching entries +- [#6762](https://github.com/mruby/mruby/pull/6762) Write generated test C files atomically to avoid build race condition +- [#6765](https://github.com/mruby/mruby/pull/6765) Fix `Lazy#flat_map` to handle non-enumerable block return values +- [#6767](https://github.com/mruby/mruby/pull/6767) Allow compound statement in parenthesized argument context +- [#6780](https://github.com/mruby/mruby/pull/6780) Fix `String#prepend` with self-referencing arguments +- [#6781](https://github.com/mruby/mruby/pull/6781) Protect `sprintf` format string from mutation during callbacks +- [#6783](https://github.com/mruby/mruby/pull/6783) Pin GitHub Actions workflows to commit hashes # Security Fixes @@ -320,4 +360,8 @@ Other optimizations: - Heap-use-after-free in insertion_sort ([099d2c47](https://github.com/mruby/mruby/commit/099d2c47)) - Integer overflow in str_check_length ([6afff1c3](https://github.com/mruby/mruby/commit/6afff1c3)) - Integer overflow in Integer#lcm ([070bef24](https://github.com/mruby/mruby/commit/070bef24)) +- Heap buffer overflow in `#method_missing` ([550d10a](https://github.com/mruby/mruby/commit/550d10a)) +- Out-of-bounds read and divide-by-zero in `Array#product` ([8441eaf](https://github.com/mruby/mruby/commit/8441eaf)) +- Heap buffer overflow in `String#prepend` with self-referencing arguments ([18ba026](https://github.com/mruby/mruby/commit/18ba026)) +- Use-after-free in `sprintf` via `to_s` callback mutating format string ([48fc422](https://github.com/mruby/mruby/commit/48fc422)) - Multiple memory leak fixes in bigint, Set, Array, and Task gems diff --git a/mrbgems/mruby-sprintf/src/sprintf.c b/mrbgems/mruby-sprintf/src/sprintf.c index 66f960d77..c7fbf4397 100644 --- a/mrbgems/mruby-sprintf/src/sprintf.c +++ b/mrbgems/mruby-sprintf/src/sprintf.c @@ -380,6 +380,13 @@ mrb_str_format(mrb_state *mrb, mrb_int argc, const mrb_value *argv, mrb_value fm argc++; argv--; mrb_ensure_string_type(mrb, fmt); + /* Duplicate the format string so that to_s/inspect callbacks invoked + during the loop cannot invalidate p/end by mutating the original + via String#replace or similar. mrb_str_dup shares the underlying + buffer, so this is O(1); String#replace on the original goes + through str_replace which decrements the shared refcount, leaving + our copy's buffer intact. */ + fmt = mrb_str_dup(mrb, fmt); p = RSTRING_PTR(fmt); end = p + RSTRING_LEN(fmt); blen = 0; diff --git a/mrbgems/mruby-sprintf/test/sprintf.rb b/mrbgems/mruby-sprintf/test/sprintf.rb index 1b1fe95f1..80220137a 100644 --- a/mrbgems/mruby-sprintf/test/sprintf.rb +++ b/mrbgems/mruby-sprintf/test/sprintf.rb @@ -90,3 +90,19 @@ assert("String#% invalid format") do "%?" % "" end end + +assert("sprintf with to_s mutating format string") do + # The to_s callback must not be able to invalidate sprintf's internal + # iteration pointers by mutating the format string. + fmt = "%s" + "B" * 200 + mutator = Object.new + $sprintf_test_fmt = fmt + def mutator.to_s + $sprintf_test_fmt.replace("Z") + "ok" + end + result = sprintf(fmt, mutator) + assert_equal 202, result.length + assert_equal "ok", result[0, 2] + assert_equal "B" * 200, result[2..] +end diff --git a/mrbgems/mruby-string-ext/src/string.c b/mrbgems/mruby-string-ext/src/string.c index 331aae7f1..6d3b408c8 100644 --- a/mrbgems/mruby-string-ext/src/string.c +++ b/mrbgems/mruby-string-ext/src/string.c @@ -2201,15 +2201,27 @@ str_prepend(mrb_state *mrb, mrb_value self) char *p = RSTRING_PTR(self); - /* Move original content to the end */ + /* Move original content to the end. The original self data now lives + at p + total_prepend_len, which we use as the source for any + self-referencing arguments (e.g., s.prepend(s, s)) to avoid reading + data that has already been overwritten by earlier copies. */ memmove(p + total_prepend_len, p, self_len); /* Copy prepended strings in order */ mrb_int offset = 0; for (mrb_int i = 0; i < argc; i++) { - mrb_int arg_len = RSTRING_LEN(argv[i]); + const char *src; + mrb_int arg_len; + if (mrb_obj_eq(mrb, self, argv[i])) { + src = p + total_prepend_len; + arg_len = self_len; + } + else { + src = RSTRING_PTR(argv[i]); + arg_len = RSTRING_LEN(argv[i]); + } if (arg_len > 0) { - memcpy(p + offset, RSTRING_PTR(argv[i]), arg_len); + memcpy(p + offset, src, arg_len); offset += arg_len; } } diff --git a/mrbgems/mruby-string-ext/test/string.rb b/mrbgems/mruby-string-ext/test/string.rb index db67da7d0..44281d390 100644 --- a/mrbgems/mruby-string-ext/test/string.rb +++ b/mrbgems/mruby-string-ext/test/string.rb @@ -492,6 +492,21 @@ assert('String#prepend') do g = "world" assert_equal "hello world", g.prepend("", "hello ", "") assert_equal "hello world", g + + # Self-referencing arguments (GHSA-3hgj-g76g-878c) + h = "A" * 100 + h.prepend(h, h) + assert_equal 300, h.length + assert_equal "A" * 300, h + + # Mixed self-reference and literal + i = "AB" + i.prepend("XYZ", i) + assert_equal "XYZABAB", i + + j = "AB" + j.prepend(j, "X", j) + assert_equal "ABXABAB", j end assert('String#ljust') do