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
6 changes: 3 additions & 3 deletions array.c
Original file line number Diff line number Diff line change
Expand Up @@ -7009,7 +7009,7 @@ ary_sample(rb_execution_context_t *ec, VALUE ary, VALUE randgen, VALUE nv, VALUE
len = RARRAY_LEN(ary);
if (len < k && n <= numberof(idx)) {
for (i = 0; i < n; ++i) {
if (rnds[i] >= len - 1) return rb_ary_new_capa(0);
if (rnds[i] >= len - i) return rb_ary_new_capa(0);
}
}
if (n > len) n = len;
Expand Down Expand Up @@ -7133,8 +7133,8 @@ rb_ary_cycle_size(VALUE self, VALUE args, VALUE eobj)
if (NIL_P(n)) return DBL2NUM(HUGE_VAL);
mul = NUM2LONG(n);
if (mul <= 0) return INT2FIX(0);
n = LONG2FIX(mul);
return rb_fix_mul_fix(rb_ary_length(self), n);
n = LONG2NUM(mul);
return rb_int_mul(rb_ary_length(self), n);
}

/*
Expand Down
2 changes: 1 addition & 1 deletion enum.c
Original file line number Diff line number Diff line change
Expand Up @@ -3764,7 +3764,7 @@ enum_cycle_size(VALUE self, VALUE args, VALUE eobj)

if (NIL_P(n)) return DBL2NUM(HUGE_VAL);
if (mul <= 0) return INT2FIX(0);
n = LONG2FIX(mul);
n = LONG2NUM(mul);
return rb_funcallv(size, '*', 1, &n);
}

Expand Down
42 changes: 33 additions & 9 deletions file.c
Original file line number Diff line number Diff line change
Expand Up @@ -2232,11 +2232,23 @@ rb_file_world_readable_p(VALUE obj, VALUE fname)
* call-seq:
* File.writable?(object) -> true or false
*
* Returns +true+ if the named file is writable by the effective user and
* group id of this process. See <code>eaccess(3)</code>.
* Returns whether given `object` exists and is writable by the owner and group
* in the current process:
*
* Note that some OS-level security features may cause this to return true
* even though the file is not writable by the effective user/group.
* ```ruby
* filepath = '/tmp/secret.txt'
* File.writable?(filepath) # => false # Non-existent.
* File.write(filepath, 'foo') # Create file.
* File.writable?(filepath) # => true # Writable.
* File.chmod(0o000, filepath) # Make non-writable.
* File.writable?(filepath) # => false # Not writable.
* File.delete(filepath) # Clean up.
* File.writable?('/etc') # => false # Directory.
* File.writable?($stdin) # => false # IO object.
* ```
*
* Note that filesystem security features may cause this method to return `true`
* even when the file is not writable by the owner and group.
*/

static VALUE
Expand Down Expand Up @@ -7210,14 +7222,26 @@ rb_stat_wr(VALUE obj)
}

/*
* call-seq:
* stat.writable? -> true or false
* :markup: markdown

* call-seq:
* writable? -> true or false
*
* Returns +true+ if <i>stat</i> is writable by the effective user id of this
* process.
* Returns whether the entry at the path in `self` exists and is writable
* by the effective owner and group in the current process:
*
* File.stat("testfile").writable? #=> true
* ```ruby
* filepath = '/tmp/secret.txt'
* File.write(filepath, 'foo')
* File.stat(filepath).writable? # => true # Writable.
* File.chmod(0o000, filepath) # Make non-writable.
* File.stat(filepath).writable? # => false # Not writable.
* File.delete(filepath) # Clean up.
* File.stat('/etc').writable? # => false # Directory.
* ```
*
* Note that filesystem security features may cause this method to return `true`
* even when the file is not writable by the effective owner and group.
*/

static VALUE
Expand Down
26 changes: 2 additions & 24 deletions lib/rubygems/installer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -628,10 +628,10 @@ def shebang(bin_file_name)
# installation.

def ensure_loadable_spec
ruby = spec.to_ruby_for_cache
spec = self.spec

begin
eval ruby
eval spec.to_ruby_for_cache
rescue StandardError, SyntaxError => e
raise Gem::InstallError,
"The specification for #{spec.full_name} is corrupt (#{e.class})"
Expand Down Expand Up @@ -723,30 +723,10 @@ def verify_spec
raise Gem::InstallError, "#{spec} has an invalid version"
end

if spec.raw_require_paths.any? {|path| path =~ /\R/ }
raise Gem::InstallError, "#{spec} has an invalid require_paths"
end

if spec.extensions.any? {|ext| ext =~ /\R/ }
raise Gem::InstallError, "#{spec} has an invalid extensions"
end

unless /\A[\w.-]+\z/.match?(spec.platform.to_s)
raise Gem::InstallError, "#{spec.platform} is an invalid platform"
end

unless /\A\d+\z/.match?(spec.specification_version.to_s)
raise Gem::InstallError, "#{spec} has an invalid specification_version"
end

if spec.dependencies.any? {|dep| dep.type != :runtime && dep.type != :development }
raise Gem::InstallError, "#{spec} has an invalid dependencies"
end

if spec.dependencies.any? {|dep| dep.name =~ /(?:\R|[<>])/ }
raise Gem::InstallError, "#{spec} has an invalid dependencies"
end

if spec.executables.any? {|name| !name.is_a?(String) || name != File.basename(name) || /\A\.\.?\z|\R/.match?(name) }
raise Gem::InstallError, "#{spec} has an invalid executable"
end
Expand Down Expand Up @@ -945,8 +925,6 @@ def gem
def pre_install_checks
verify_gem_home

# The name and require_paths must be verified first, since it could contain
# ruby code that would be eval'ed in #ensure_loadable_spec
verify_spec

ensure_loadable_spec
Expand Down
13 changes: 12 additions & 1 deletion lib/rubygems/specification.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2420,6 +2420,10 @@ def to_ruby
result << "#{Gem::StubSpecification::PREFIX}#{extensions.join "\0"}" unless
extensions.empty?
result << "#{Gem::StubSpecification::TARGET_PREFIX}platform=#{platform}" if content_addressed
# Comments can't be escaped, so a newline would end the stub line early.
result.each do |line|
raise Gem::Exception, "stub line #{line.dump} contains a newline" if line.include?("\n")
end
result << nil
result << "Gem::Specification.new do |s|"

Expand Down Expand Up @@ -2467,13 +2471,20 @@ def to_ruby
end

unless dependencies.empty?
unless Integer === specification_version
raise Gem::Exception, "invalid specification_version: #{specification_version.inspect}"
end

result << nil
result << " s.specification_version = #{specification_version}"
result << nil

dependencies.each do |dep|
dep.instance_variable_set :@type, :runtime if dep.type.nil? # HACK
result << " s.add_#{dep.type}_dependency(%q<#{dep.name}>.freeze, #{ruby_code dep.requirements_list})"
unless Gem::Dependency::TYPES.include?(dep.type)
raise Gem::Exception, "invalid dependency type: #{dep.type.inspect}"
end
result << " s.add_#{dep.type}_dependency(#{ruby_code dep.name}, #{ruby_code dep.requirements_list})"
end
end

Expand Down
19 changes: 11 additions & 8 deletions pathname_builtin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2742,17 +2742,20 @@ def symlink?() FileTest.symlink?(@path) end
# writable? => true or false
#
# Returns whether entry at the path in `self`
# is writable by the owner and group of the current process:
# exists and is writable by the effective owner and group of the current process:
#
# ```ruby
# pn = Pathname('/tmp/secret.txt')
# pn.write('foo')
# pn.writable? # => true
# pn.chmod(0o000)
# pn.writable? # => false
# pn.delete
# Pathname('nosuch').writable? # => false
# ```
# pn.writable? # => false # Non-existent.
# pn.write('foo') # Create the file.
# pn.writable? # => true # Writable.
# pn.chmod(0o000) # Make non-writable.
# pn.writable? # => false # Not writable.
# pn.delete # Clean up.
# Pathname('/etc/').writable? # => false # Directory.
# ```
# Note that filesystem security features may cause this method to return true
# even when the entry is not writable by the effective owner and group.
#
def writable?() FileTest.writable?(@path) end

Expand Down
10 changes: 10 additions & 0 deletions test/ruby/test_array.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3417,6 +3417,16 @@ def test_sample_modify_array_out_of_bounds
# 49 will be out-of-bounds when ary.replace is called
def gen.rand(lim) = 49
assert_equal([], ary.sample(obj, random: gen))

ary = (1..100).to_a
obj = Object.new
obj.define_singleton_method(:to_int) do
ary.replace(Array.new(10) { :x })
10
end
gen = Object.new
gen.define_singleton_method(:rand) { |lim| 8 }
assert_equal([], ary.sample(obj, random: gen))
end

def test_cycle
Expand Down
3 changes: 3 additions & 0 deletions test/ruby/test_enumerator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,9 @@ def test_size_for_cycle
assert_equal 0, [].cycle(5).size
assert_equal 0, {}.cycle.size
assert_equal 0, {}.cycle(5).size
n = RbConfig::LIMITS["FIXNUM_MAX"] + 1
assert_equal 2 * n, [:foo, :bar].cycle(n).size
assert_equal 2 * n, {foo: 1, bar: 2}.cycle(n).size

assert_equal nil, @obj.cycle.size
assert_equal nil, @obj.cycle(5).size
Expand Down
1 change: 1 addition & 0 deletions test/ruby/test_ractor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ def test_failed_send_leaves_receiver_usable
end

def test_sending_hook_payloads_under_gc_stress
pend "SIGSEGV intermittently on Windows arm64" if /\A(?:arm64|aarch64)-(?:mswin|mingw)/ =~ RUBY_PLATFORM
# A dump hook's payload is garbage once captured. A later payload allocated into
# its slot must not be taken for the one already seen.
assert_ractor(<<~'RUBY', timeout: 60)
Expand Down
29 changes: 13 additions & 16 deletions test/rubygems/test_gem_installer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -294,17 +294,19 @@ def test_ensure_dependency

def test_ensure_loadable_spec
a, a_gem = util_gem "a", 2 do |s|
s.add_dependency "garbage ~> 5"
s.add_dependency "b"
end

installer = Gem::Installer.at a_gem
requirement = installer.spec.dependencies.first.requirement
requirement.instance_variable_set :@requirements, [["garbage", Gem::Version.new(5)]]

e = assert_raise Gem::InstallError do
installer.ensure_loadable_spec
end

assert_equal "The specification for #{a.full_name} is corrupt " \
"(SyntaxError)", e.message
"(Gem::Requirement::BadRequirementError)", e.message
end

def test_ensure_loadable_spec_security_policy
Expand Down Expand Up @@ -2426,7 +2428,7 @@ def spec.validate(*args); end
e = assert_raise Gem::InstallError do
installer.pre_install_checks
end
assert_equal "#<Gem::Specification name=malicious version=1> has an invalid require_paths", e.message
assert_equal "The specification for malicious-1 is corrupt (Gem::Exception)", e.message
end
end

Expand All @@ -2442,7 +2444,7 @@ def spec.validate(*args); end
e = assert_raise Gem::InstallError do
installer.pre_install_checks
end
assert_equal "#<Gem::Specification name=malicious version=1> has an invalid extensions", e.message
assert_equal "The specification for malicious-1 is corrupt (Gem::Exception)", e.message
end
end

Expand All @@ -2453,6 +2455,7 @@ def spec.full_name # so the spec is buildable
end

def spec.validate(*args); end
spec.add_dependency "b"
spec.specification_version = "malicious\n``"

util_build_gem spec
Expand All @@ -2464,30 +2467,24 @@ def spec.validate(*args); end
e = assert_raise Gem::InstallError do
installer.pre_install_checks
end
assert_equal "#<Gem::Specification name=malicious version=1> has an invalid specification_version", e.message
assert_equal "The specification for malicious-1 is corrupt (Gem::Exception)", e.message
end
end

def test_pre_install_checks_malicious_dependencies_before_eval
spec = util_spec "malicious", "1"
def spec.full_name # so the spec is buildable
"malicious-1"
end

def spec.validate(*args); end
spec.add_dependency "b\nfoo", "> 5"
spec.add_dependency "b", "> 5"
spec.dependencies.first.instance_variable_set :@type, :foo

util_build_gem spec

gem = File.join(@gemhome, "cache", spec.file_name)
installer = Gem::Installer.for_spec spec
installer.gem_home = @gemhome

use_ui @ui do
installer = Gem::Installer.at gem
installer.ignore_dependencies = true
e = assert_raise Gem::InstallError do
installer.pre_install_checks
end
assert_equal "#<Gem::Specification name=malicious version=1> has an invalid dependencies", e.message
assert_equal "The specification for malicious-1 is corrupt (Gem::Exception)", e.message
end
end

Expand Down
Loading