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
28 changes: 28 additions & 0 deletions .github/actions/setup/compilation-source/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Set up sources for Compilations
description: Download and extract sources prepared by the Compilations workflow.

runs:
using: composite
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: compilation-source
path: ${{ runner.temp }}

- shell: bash
working-directory: ${{ github.workspace }}
run: tar -xf "${RUNNER_TEMP}/compilation-source.tar"

- id: gems-key
shell: bash
run: echo "hash=${HASH}" >> "${GITHUB_OUTPUT}"
env:
HASH: ${{ hashFiles('src/gems/bundled_gems') }}

- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: src/.downloaded-cache
key: downloaded-cache-${{ steps.gems-key.outputs.hash }}
restore-keys: |
downloaded-cache-
${{ runner.os }}-${{ runner.arch }}-downloaded-cache
399 changes: 163 additions & 236 deletions .github/workflows/compilers.yml

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions doc/language/exceptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ A raised exception transfers program execution, one way or another.

### Unrescued Exceptions

If an exception not _rescued_
If an exception is not _rescued_
(see [Rescued Exceptions](#label-Rescued+Exceptions) below),
execution transfers to code in the Ruby interpreter
that prints a message and exits the program (or thread):
Expand Down Expand Up @@ -63,7 +63,7 @@ An exception handler has several elements:
| One or more rescue clauses. | Each contains "rescuing" code, which is to be executed for certain exceptions. |
| Else clause (optional). | Contains code to be executed if no exception is raised. |
| Ensure clause (optional). | Contains code to be executed whether or not an exception is raised, or is rescued. |
| <tt>end</tt> statement. | Ends the handler. ` |
| <tt>end</tt> statement. | Ends the handler. |

#### Begin Clause

Expand Down Expand Up @@ -159,7 +159,7 @@ Rescued Errno::ENOENT

A `rescue` statement may specify a variable
whose value becomes the rescued exception
(an instance of Exception or one of its subclasses:
(an instance of Exception or one of its subclasses):

```rb
begin
Expand Down
29 changes: 20 additions & 9 deletions lib/rubygems/safe_marshal/reader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ class DataTooShortError < Error
class NegativeLengthError < Error
end

class LengthTooLongError < Error
end

def initialize(io)
@io = io
@object_links = {}
Expand Down Expand Up @@ -93,6 +96,18 @@ def read_integer
end
end

# Reads an element count and validates it against the number of bytes
# remaining in the input, since each element to be read consumes at
# least one byte. This prevents allocating huge backing stores for
# maliciously crafted lengths that could never be satisfied.
def read_count
count = read_integer
raise NegativeLengthError if count < 0
remaining = @io.size - @io.pos
raise LengthTooLongError, "expected #{count} elements, but only #{remaining} bytes remain" if count > remaining
count
end

def read_element
type = read_byte
case type
Expand Down Expand Up @@ -172,9 +187,8 @@ def read_user_defined
private_constant :EMPTY_ARRAY

def read_array
length = read_integer
length = read_count
return EMPTY_ARRAY if length == 0
raise NegativeLengthError if length < 0
elements = Array.new(length) do
read_element
end
Expand All @@ -183,8 +197,7 @@ def read_array

def read_object_with_ivars
object = read_element
length = read_integer
raise NegativeLengthError if length < 0
length = read_count
ivars = Array.new(length) do
[read_element, read_element]
end
Expand All @@ -211,7 +224,7 @@ def read_object_link
private_constant :EMPTY_HASH

def read_hash
length = read_integer
length = read_count
return EMPTY_HASH if length == 0
pairs = Array.new(length) do
[read_element, read_element]
Expand All @@ -220,8 +233,7 @@ def read_hash
end

def read_hash_with_default_value
length = read_integer
raise NegativeLengthError if length < 0
length = read_count
pairs = Array.new(length) do
[read_element, read_element]
end
Expand All @@ -232,8 +244,7 @@ def read_hash_with_default_value
def read_object
name = read_element
object = Elements::Object.new(name)
length = read_integer
raise NegativeLengthError if length < 0
length = read_count
ivars = Array.new(length) do
[read_element, read_element]
end
Expand Down
4 changes: 4 additions & 0 deletions marshal.c
Original file line number Diff line number Diff line change
Expand Up @@ -1487,6 +1487,10 @@ r_bytes1_buffered(long len, struct load_arg *arg)

if (tmp_len > need_len) {
buflen = tmp_len - need_len;
if (UNLIKELY(buflen > arg->bufsize)) {
arg->buf = ruby_sized_realloc_n(arg->buf, buflen, 1, arg->bufsize);
arg->bufsize = buflen;
}
memcpy(arg->buf, RSTRING_PTR(tmp)+need_len, buflen);
arg->buflen = buflen;
}
Expand Down
22 changes: 22 additions & 0 deletions test/ruby/test_marshal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -953,6 +953,28 @@ def read(_len, _outbuf = nil)
assert_equal([nil, nil], Marshal.load(input))
end

def test_load_overread_string_body
input = Struct.new(:bytes, :count) do
def initialize
super("\x04\x08[\x07".bytes, 0)
end

def getbyte
bytes.shift
end

def read(_len, _outbuf = nil)
self.count += 1
case count
when 1 then "\"\x06" # TYPE_STRING, length 1
when 2 then "a" + "0" * (1024 * 128)
end
end
end.new

assert_equal(["a", nil], Marshal.load(input))
end

def test_bignum_len_overflow
assert_raise(ArgumentError) do
Marshal.load("\x04\x08l+\x04\x00\x00\x00\x40")
Expand Down
31 changes: 29 additions & 2 deletions test/rubygems/test_gem_safe_marshal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -423,10 +423,10 @@ def test_unexpected_eof
end
assert_equal e.message, "Unexpected EOF"

e = assert_raise(Gem::SafeMarshal::Reader::EOFError) do
e = assert_raise(Gem::SafeMarshal::Reader::LengthTooLongError) do
Gem::SafeMarshal.safe_load("\x04\x08[\x06")
end
assert_equal e.message, "Unexpected EOF"
assert_equal e.message, "expected 1 elements, but only 0 bytes remain"

e = assert_raise(Gem::SafeMarshal::Reader::EOFError) do
Gem::SafeMarshal.safe_load("\004\010:\012")
Expand Down Expand Up @@ -459,6 +459,33 @@ def test_negative_length
assert_raise(Gem::SafeMarshal::Reader::EOFError) do
Gem::SafeMarshal.safe_load("\004\010@\377")
end
assert_raise(Gem::SafeMarshal::Reader::NegativeLengthError) do
Gem::SafeMarshal.safe_load("\004\010{\325")
end
end

def test_length_too_long
huge_length = "\x04#{[2_000_000_000].pack("V")}".b

assert_raise(Gem::SafeMarshal::Reader::LengthTooLongError) do
Gem::SafeMarshal.safe_load("\x04\x08[#{huge_length}")
end
assert_raise(Gem::SafeMarshal::Reader::LengthTooLongError) do
Gem::SafeMarshal.safe_load("\x04\x08{#{huge_length}")
end
assert_raise(Gem::SafeMarshal::Reader::LengthTooLongError) do
Gem::SafeMarshal.safe_load("\x04\x08}#{huge_length}")
end
assert_raise(Gem::SafeMarshal::Reader::LengthTooLongError) do
Gem::SafeMarshal.safe_load("\x04\x08I\"\x00#{huge_length}")
end
assert_raise(Gem::SafeMarshal::Reader::LengthTooLongError) do
Gem::SafeMarshal.safe_load("\x04\x08o:\x06C#{huge_length}")
end

# lengths that fit within the remaining input still parse
assert_equal [1, 2, 3], Gem::SafeMarshal.safe_load("\x04\x08[\x08i\x06i\ai\x08")
assert_equal({ 1 => 2 }, Gem::SafeMarshal.safe_load("\x04\x08{\x06i\x06i\a"))
end

def test_date_user_defined_rejected
Expand Down