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
30 changes: 16 additions & 14 deletions array.c
Original file line number Diff line number Diff line change
Expand Up @@ -2937,27 +2937,29 @@ rb_ary_resurrect(VALUE ary)

#if USE_ZJIT
bool
rb_zjit_array_dup_can_fastpath(VALUE ary, size_t *alloc_size_out, VALUE *flags_out, long *len_out)
rb_zjit_array_new_can_fastpath(long len, size_t *alloc_size_out, VALUE *flags_out)
{
long len = RARRAY_LEN(ary);
long embed_capa = (sizeof(struct RArray) - offsetof(struct RArray, as.ary)) / sizeof(VALUE);

if (len > embed_capa) return false;
if (!ary_embeddable_p(len)) {
return false;
}
long embed_size = ary_embed_size(len);

*alloc_size_out = sizeof(struct RArray);
*alloc_size_out = embed_size;
*flags_out = T_ARRAY | RARRAY_EMBED_FLAG | ((VALUE)len << RARRAY_EMBED_LEN_SHIFT);
*len_out = len;
return true;
}

void
rb_zjit_array_new_fastpath(size_t *alloc_size_out, VALUE *flags_out)
bool
rb_zjit_array_dup_can_fastpath(VALUE ary, size_t *alloc_size_out, VALUE *flags_out, long *len_out)
{
size_t size = sizeof(struct RArray);
shape_id_t shape_id = rb_shape_transition_slot_size(ROOT_SHAPE_ID | SHAPE_ID_LAYOUT_OTHER,
rb_gc_size_slot_size(size));
*alloc_size_out = size;
*flags_out = T_ARRAY | RARRAY_EMBED_FLAG | ((VALUE)shape_id << SHAPE_FLAG_SHIFT);
long len = RARRAY_LEN(ary);
if (!rb_zjit_array_new_can_fastpath(len, alloc_size_out, flags_out)) {
return false;
}
else {
*len_out = len;
return true;
}
}
#endif

Expand Down
2 changes: 1 addition & 1 deletion gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -4210,7 +4210,7 @@ rb_gc_obj_foreign_p(VALUE obj)
bool
rb_gc_single_objspace_p(void)
{
if (!rb_gc_impl_multi_objspace_p()) return true;
if (!rb_gc_impl_multi_objspace_p() || ruby_single_main_ractor) return true;
rb_vm_t *vm = GET_VM();
return vm->ractor.cnt == 1 && vm->gc.zombie_objspaces_count == 0 && gc_absorbing_zombie == 0 &&
!gc_absorbed_since_global_gc &&
Expand Down
20 changes: 8 additions & 12 deletions gc/default/default.c
Original file line number Diff line number Diff line change
Expand Up @@ -1677,7 +1677,7 @@ RVALUE_UNCOLLECTIBLE(rb_objspace_t *objspace, VALUE obj)
#define RVALUE_PAGE_UNCOLLECTIBLE(page, obj) MARKED_IN_BITMAP((page)->uncollectible_bits, (obj))
#define RVALUE_PAGE_MARKING(page, obj) MARKED_IN_BITMAP((page)->marking_bits, (obj))

static int rgengc_remember(rb_objspace_t *objspace, VALUE obj);
static void rgengc_remember(rb_objspace_t *objspace, VALUE obj);
static void gc_bitmaps_clear(rb_objspace_t *objspace, rb_heap_t *heap, bool clear_shref);
static void rgengc_rememberset_mark(rb_objspace_t *objspace, rb_heap_t *heap);
static bool verify_pointer_in_any_heap_p(const void *ptr); /* cross-objspace ownership test */
Expand Down Expand Up @@ -5786,10 +5786,10 @@ gc_pin(rb_objspace_t *objspace, VALUE obj)
{
GC_ASSERT(!SPECIAL_CONST_P(obj));

/* Never write a foreign page's pinned bit (a global GC may: everyone is stopped). */
if (gc_skip_foreign_object_p(objspace, obj)) return;

if (RB_UNLIKELY(objspace->flags.during_compacting)) {
/* Never write a foreign page's pinned bit (a global GC may: everyone is stopped). */
if (gc_skip_foreign_object_p(objspace, obj)) return;

if (RB_LIKELY(during_gc)) {
if (!RVALUE_PINNED(objspace, obj)) {
GC_ASSERT(GET_HEAP_PAGE(obj)->pinned_slots <= GET_HEAP_PAGE(obj)->total_slots);
Expand Down Expand Up @@ -7553,7 +7553,7 @@ gc_report_body(int level, rb_objspace_t *objspace, const char *fmt, ...)

/* bit operations */

static int
static void
rgengc_remembersetbits_set(rb_objspace_t *objspace, VALUE obj)
{
struct heap_page *page = GET_HEAP_PAGE(obj);
Expand All @@ -7563,16 +7563,14 @@ rgengc_remembersetbits_set(rb_objspace_t *objspace, VALUE obj)
* local a (under its Ractor's GVL) and a global GC writes from the driver alone.
* Set the bit before the page flag so a page pending re-scan stays in
* rememberset_mark. */
const bool newly = !_MARKED_IN_BITMAP(bits, page, obj);
_MARK_IN_BITMAP(bits, page, obj);
page->flags.has_remembered_objects = TRUE;
return newly ? TRUE : FALSE;
}

/* wb, etc */

/* return FALSE if already remembered */
static int
static void
rgengc_remember(rb_objspace_t *objspace, VALUE obj)
{
gc_report(6, objspace, "rgengc_remember: %s %s\n", rb_obj_info(obj),
Expand All @@ -7595,7 +7593,7 @@ rgengc_remember(rb_objspace_t *objspace, VALUE obj)
}
#endif /* RGENGC_PROFILE > 0 */

return rgengc_remembersetbits_set(objspace, obj);
rgengc_remembersetbits_set(objspace, obj);
}

#ifndef PROFILE_REMEMBERSET_MARK
Expand Down Expand Up @@ -7785,9 +7783,6 @@ rb_gc_impl_writebarrier(void *objspace_ptr, VALUE a, VALUE b)
GC_ASSERT(RB_BUILTIN_TYPE(a) != T_NONE);
GC_ASSERT(RB_BUILTIN_TYPE(a) != T_MOVED);
GC_ASSERT(RB_BUILTIN_TYPE(a) != T_ZOMBIE);
GC_ASSERT(RB_BUILTIN_TYPE(b) != T_NONE);
GC_ASSERT(RB_BUILTIN_TYPE(b) != T_MOVED);
GC_ASSERT(RB_BUILTIN_TYPE(b) != T_ZOMBIE);

/* A shareable object now references an unshareable one: record b as a shref so its
* owner's local GC roots it (the parent may live in another objspace, untraversed
Expand Down Expand Up @@ -7844,6 +7839,7 @@ rb_gc_impl_obj_became_shareable(void *objspace_ptr, VALUE obj)
* the only writer, so a plain clear is enough. */
if (_MARKED_IN_BITMAP(page->shref_bits, page, obj)) {
_CLEAR_IN_BITMAP(page->shref_bits, page, obj);
// NOTE: page->has_shref_objects could become stale here (value is true even though logically false)
}
}

Expand Down
4 changes: 2 additions & 2 deletions lib/bundler/man/bundle-config.1
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ The following is a list of all configuration keys and their purpose\. You can le
.IP "\(bu" 4
\fBcache_path\fR (\fBBUNDLE_CACHE_PATH\fR): The directory that bundler will place cached gems in when running \fBbundle package\fR, and that bundler will look in when installing gems\. Defaults to \fBvendor/cache\fR\.
.IP "\(bu" 4
\fBclean\fR (\fBBUNDLE_CLEAN\fR): Whether Bundler should run \fBbundle clean\fR automatically after \fBbundle install\fR\. Defaults to \fBtrue\fR in Bundler 4, as long as \fBpath\fR is not explicitly configured\.
\fBclean\fR (\fBBUNDLE_CLEAN\fR): Whether Bundler should run \fBbundle clean\fR automatically after \fBbundle install\fR\. Defaults to \fBtrue\fR in Bundler 5, as long as \fBpath\fR is not explicitly configured\.
.IP "\(bu" 4
\fBconsole\fR (\fBBUNDLE_CONSOLE\fR): The console that \fBbundle console\fR starts\. Defaults to \fBirb\fR\.
.IP "\(bu" 4
Expand Down Expand Up @@ -151,7 +151,7 @@ Cooldown filtering depends on the gem server providing a per\-version \fBcreated
.IP "\(bu" 4
\fBonly\fR (\fBBUNDLE_ONLY\fR): A space\-separated list of groups to install only gems of the specified groups\. Please check carefully if you want to install also gems without a group, because they get put inside \fBdefault\fR group\. For example \fBonly test:default\fR will install all gems specified in test group and without one\.
.IP "\(bu" 4
\fBpath\fR (\fBBUNDLE_PATH\fR): The location on disk where all gems in your bundle will be located regardless of \fB$GEM_HOME\fR or \fB$GEM_PATH\fR values\. Bundle gems not found in this location will be installed by \fBbundle install\fR\. When not set, Bundler install by default to a \fB\.bundle\fR directory relative to repository root in Bundler 4, and to the default system path (\fBGem\.dir\fR) before Bundler 4\. That means that before Bundler 4, Bundler shares this location with Rubygems, and \fBgem install \|\.\|\.\|\.\fR will have gems installed in the same location and therefore, gems installed without \fBpath\fR set will show up by calling \fBgem list\fR\. This will not be the case in Bundler 4\.
\fBpath\fR (\fBBUNDLE_PATH\fR): The location on disk where all gems in your bundle will be located regardless of \fB$GEM_HOME\fR or \fB$GEM_PATH\fR values\. Bundle gems not found in this location will be installed by \fBbundle install\fR\. When not set, Bundler install by default to a \fB\.bundle\fR directory relative to repository root in Bundler 5, and to the default system path (\fBGem\.dir\fR) before Bundler 5\. That means that before Bundler 5, Bundler shares this location with Rubygems, and \fBgem install \|\.\|\.\|\.\fR will have gems installed in the same location and therefore, gems installed without \fBpath\fR set will show up by calling \fBgem list\fR\. This will not be the case in Bundler 5\.
.IP "\(bu" 4
\fBpath\.system\fR (\fBBUNDLE_PATH__SYSTEM\fR): Whether Bundler will install gems into the default system path (\fBGem\.dir\fR)\.
.IP "\(bu" 4
Expand Down
10 changes: 5 additions & 5 deletions lib/bundler/man/bundle-config.1.ronn
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html).
Defaults to `vendor/cache`.
* `clean` (`BUNDLE_CLEAN`):
Whether Bundler should run `bundle clean` automatically after
`bundle install`. Defaults to `true` in Bundler 4, as long as `path` is not
`bundle install`. Defaults to `true` in Bundler 5, as long as `path` is not
explicitly configured.
* `console` (`BUNDLE_CONSOLE`):
The console that `bundle console` starts. Defaults to `irb`.
Expand Down Expand Up @@ -253,12 +253,12 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html).
The location on disk where all gems in your bundle will be located regardless
of `$GEM_HOME` or `$GEM_PATH` values. Bundle gems not found in this location
will be installed by `bundle install`. When not set, Bundler install by
default to a `.bundle` directory relative to repository root in Bundler 4,
and to the default system path (`Gem.dir`) before Bundler 4. That means that
before Bundler 4, Bundler shares this location with Rubygems, and `gem
default to a `.bundle` directory relative to repository root in Bundler 5,
and to the default system path (`Gem.dir`) before Bundler 5. That means that
before Bundler 5, Bundler shares this location with Rubygems, and `gem
install ...` will have gems installed in the same location and therefore,
gems installed without `path` set will show up by calling `gem list`. This
will not be the case in Bundler 4.
will not be the case in Bundler 5.
* `path.system` (`BUNDLE_PATH__SYSTEM`):
Whether Bundler will install gems into the default system path (`Gem.dir`).
* `plugins` (`BUNDLE_PLUGINS`):
Expand Down
11 changes: 6 additions & 5 deletions lib/bundler/source/git/git_proxy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -364,9 +364,10 @@ def verify(reference)
git("rev-parse", "--verify", reference, dir: path).strip
end

# Adds credentials to the URI
# Adds credentials to the URI. This is the URI given to git, so it's
# also the one command output must be filtered against.
def configured_uri
if /https?:/.match?(uri)
@configured_uri ||= if /https?:/.match?(uri)
remote = Gem::URI(uri)
config_auth = Bundler.settings[remote.to_s] || Bundler.settings[remote.host]
remote.userinfo ||= config_auth
Expand Down Expand Up @@ -409,7 +410,7 @@ def redact_and_check_presence(command)
raise GitNotInstalledError.new unless Bundler.git_present?

require "shellwords"
URICredentialsFilter.credential_filtered_string("git #{command.shelljoin}", uri)
URICredentialsFilter.credential_filtered_string("git #{command.shelljoin}", configured_uri)
end

def run_command(*command, dir: nil)
Expand All @@ -429,10 +430,10 @@ def capture(cmd, dir, ignore_err: false)
require "open3"
out, err, status = Open3.capture3(*capture3_args_for(cmd, dir))

filtered_out = URICredentialsFilter.credential_filtered_string(out, uri)
filtered_out = URICredentialsFilter.credential_filtered_string(out, configured_uri)
return [filtered_out, status] if ignore_err

filtered_err = URICredentialsFilter.credential_filtered_string(err, uri)
filtered_err = URICredentialsFilter.credential_filtered_string(err, configured_uri)
[filtered_out, filtered_err, status]
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/rubygems/installer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -697,7 +697,7 @@ def verify_spec
raise Gem::InstallError, "#{spec} has an invalid extensions"
end

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

Expand Down
9 changes: 8 additions & 1 deletion lib/rubygems/safe_marshal/visitors/to_ruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,15 @@ def visit_Gem_SafeMarshal_Elements_SymbolLink(o)
@symbols.fetch(o.offset)
end

SAFE_USER_DEFINED_CLASSES = [::Time, ::Gem::Specification].freeze
private_constant :SAFE_USER_DEFINED_CLASSES

def visit_Gem_SafeMarshal_Elements_UserDefined(o)
register_object(call_method(resolve_class(o.name), :_load, o.binary_string))
klass = resolve_class(o.name)
unless SAFE_USER_DEFINED_CLASSES.include?(klass)
raise UnsupportedError.new("Unsupported user-defined class #{klass} in marshal stream", stack: formatted_stack)
end
register_object(call_method(klass, :_load, o.binary_string))
end

def visit_Gem_SafeMarshal_Elements_UserMarshal(o)
Expand Down
62 changes: 62 additions & 0 deletions spec/bundler/bundler/source/git/git_proxy_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,68 @@
end
end

describe "filtering credentials out of command output" do
let(:secret) { "s3cr3tp4ss" }
let(:credentialed_uri) { "https://user:#{secret}@github.com/ruby/rubygems.git" }
let(:redacted_uri) { "https://user@github.com/ruby/rubygems.git" }

before do
allow(Open3).to receive(:capture3).and_return(["", "fatal: repository '#{credentialed_uri}' not found", fail_result])
allow(Open3).to receive(:capture3).with("git", "--version").and_return(["git version 2.14.0", "", clone_result])
end

it "redacts credentials configured for the host from failed commands" do
Bundler.settings.temporary("github.com" => "user:#{secret}") do
expect { git_proxy.checkout }.to raise_error(Bundler::Source::Git::GitCommandError) do |error|
expect(error.message).not_to include(secret)
expect(error.message).to include(redacted_uri)
end
end
end

it "redacts credentials configured for the full URI from failed commands" do
Bundler.settings.temporary(uri => "user:#{secret}") do
expect { git_proxy.checkout }.to raise_error(Bundler::Source::Git::GitCommandError) do |error|
expect(error.message).not_to include(secret)
expect(error.message).to include(redacted_uri)
end
end
end

it "redacts configured credentials from stdout and stderr" do
Bundler.settings.temporary("github.com" => "user:#{secret}") do
allow(Open3).to receive(:capture3).and_return(["cloning #{credentialed_uri}", "error: #{credentialed_uri}", fail_result])

out, err, = git_proxy.send(:capture, ["fetch"], nil)

expect(out).to eq("cloning #{redacted_uri}")
expect(err).to eq("error: #{redacted_uri}")
end
end

context "when the URI itself embeds credentials" do
let(:uri) { credentialed_uri }

it "redacts them from failed commands" do
expect { git_proxy.checkout }.to raise_error(Bundler::Source::Git::GitCommandError) do |error|
expect(error.message).not_to include(secret)
expect(error.message).to include(redacted_uri)
end
end
end

context "when no credentials are involved" do
it "leaves output untouched" do
allow(Open3).to receive(:capture3).and_return(["cloning #{uri}", "error: #{uri}", fail_result])

out, err, = git_proxy.send(:capture, ["fetch"], nil)

expect(out).to eq("cloning #{uri}")
expect(err).to eq("error: #{uri}")
end
end
end

describe "#copy_to" do
let(:revision) { "abc123" }
let(:destination) { tmp("git-proxy-copy") }
Expand Down
56 changes: 56 additions & 0 deletions test/rubygems/test_gem_installer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2057,6 +2057,62 @@ def test_pre_install_checks_malicious_platform_before_eval
end
end

# Psych assigns @cpu/@os/@version directly, so a platform coming from gem
# metadata never goes through Gem::Platform#initialize.
def test_pre_install_checks_traversal_platform_from_array
spec = util_spec "a", "1"
def spec.validate(*args); end
spec.platform = Gem::Platform.new([nil, "../../../../tmp/X", nil])

installer = Gem::Installer.for_spec spec
installer.gem_home = @gemhome

use_ui @ui do
e = assert_raise Gem::InstallError do
installer.pre_install_checks
end
assert_equal "../../../../tmp/X is an invalid platform", e.message
end
end

# Gem::Platform#initialize normalizes the os but keeps the cpu verbatim.
def test_pre_install_checks_traversal_platform_from_string
spec = util_spec "a", "1"
def spec.validate(*args); end
spec.platform = Gem::Platform.new("../../../../tmp/X-linux")

installer = Gem::Installer.for_spec spec
installer.gem_home = @gemhome

use_ui @ui do
e = assert_raise Gem::InstallError do
installer.pre_install_checks
end
assert_equal "../../../../tmp/X-linux is an invalid platform", e.message
end
end

def test_pre_install_checks_accepts_real_platforms
%w[x86_64-linux arm64-darwin-23 x64-mingw-ucrt java ruby].each do |platform|
spec = util_spec "a", "1" do |s|
s.platform = platform
end

util_build_gem spec

installer = Gem::Installer.at spec.cache_file,
install_dir: @gemhome,
user_install: false,
force: true

use_ui @ui do
assert_equal spec, installer.install, platform
end

assert_path_exist File.join(@gemhome, "gems", spec.full_name), platform
end
end

def test_shebang
load_relative "no" do
installer = setup_base_installer
Expand Down
10 changes: 10 additions & 0 deletions test/rubygems/test_gem_safe_marshal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,16 @@ def test_negative_length
end
end

def test_date_user_defined_rejected
# Provide string as the inner payload, Date._load passes it raw to rb_marshal_load.
inner = Marshal.dump("exploit")
payload = "\x04\bu:\tDate" + (inner.bytesize + 5).chr + inner
e = assert_raise(Gem::SafeMarshal::Visitors::ToRuby::UnsupportedError) do
Gem::SafeMarshal.safe_load(payload)
end
assert_equal "Unsupported user-defined class Date in marshal stream @ root", e.message
end

def assert_safe_load_marshal(dumped, additional_methods: [], permitted_ivars: nil, equality: true, marshal_dump_equality: true,
inspect: true, to_s: true)
loaded = Marshal.load(dumped)
Expand Down
1 change: 1 addition & 0 deletions zjit.h
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ VALUE rb_zjit_new_obj_shape(VALUE flags, size_t alloc_size);
bool rb_zjit_class_allocate_instance_fastpath(VALUE klass, size_t *size_out, VALUE *flags_out);
bool rb_zjit_str_resurrect_fastpath(VALUE str, bool chilled, size_t *size_out, VALUE *flags_out, long *len_out, size_t *byte_size_out);
bool rb_zjit_array_dup_can_fastpath(VALUE ary, size_t *alloc_size_out, VALUE *flags_out, long *len_out);
bool rb_zjit_array_new_can_fastpath(long len, size_t *alloc_size_out, VALUE *flags_out);
bool rb_zjit_hash_dup_can_fastpath(VALUE hash, size_t *alloc_size_out, VALUE *flags_out, VALUE *ifnone_out, long *bound_out);
void rb_zjit_range_new_fastpath(bool exclude_end, size_t *alloc_size_out, VALUE *flags_out);
void rb_zjit_array_new_fastpath(size_t *alloc_size_out, VALUE *flags_out);
Expand Down
Loading