Fix HTTP response splitting in CGI. Fix ReDos vulnerability in URI. Fix ReDos vulnerability in Time. Make RDoc soft dependency in IRB. Rebased from latest Ruby 3.1 present in Fedora 37, commit: 4048f893da1d56ed65667e7f15405224653c83e2 Resolves: RHEL-5584 Resolves: CVE-2021-33621 Resolves: CVE-2023-28755 Resolves: CVE-2023-36617 Resolves: CVE-2023-28756 Resolves: RHEL-5615
503 lines
16 KiB
Diff
503 lines
16 KiB
Diff
From 1b3502156a665e2782f366aa5ac8c3bfd7637ab8 Mon Sep 17 00:00:00 2001
|
|
From: Mike Dalessio <mike.dalessio@gmail.com>
|
|
Date: Mon, 23 May 2022 15:40:22 -0400
|
|
Subject: [PATCH 1/2] Move compaction-related methods into gc.c
|
|
|
|
These methods are removed from gc.rb and added to gc.c:
|
|
|
|
- GC.compact
|
|
- GC.auto_compact
|
|
- GC.auto_compact=
|
|
- GC.latest_compact_info
|
|
- GC.verify_compaction_references
|
|
|
|
This is a prefactor to allow setting these methods to
|
|
`rb_f_notimplement` in a followup commit.
|
|
---
|
|
gc.c | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++++------
|
|
gc.rb | 68 ---------------------------------------
|
|
2 files changed, 91 insertions(+), 78 deletions(-)
|
|
|
|
diff --git a/gc.c b/gc.c
|
|
index ef9327df1f..1c35856c44 100644
|
|
--- a/gc.c
|
|
+++ b/gc.c
|
|
@@ -10165,8 +10165,20 @@ gc_update_references(rb_objspace_t *objspace)
|
|
gc_update_table_refs(objspace, finalizer_table);
|
|
}
|
|
|
|
+/*
|
|
+ * call-seq:
|
|
+ * GC.latest_compact_info -> {:considered=>{:T_CLASS=>11}, :moved=>{:T_CLASS=>11}}
|
|
+ *
|
|
+ * Returns information about object moved in the most recent GC compaction.
|
|
+ *
|
|
+ * The returned hash has two keys :considered and :moved. The hash for
|
|
+ * :considered lists the number of objects that were considered for movement
|
|
+ * by the compactor, and the :moved hash lists the number of objects that
|
|
+ * were actually moved. Some objects can't be moved (maybe they were pinned)
|
|
+ * so these numbers can be used to calculate compaction efficiency.
|
|
+ */
|
|
static VALUE
|
|
-gc_compact_stats(rb_execution_context_t *ec, VALUE self)
|
|
+gc_compact_stats(VALUE self)
|
|
{
|
|
size_t i;
|
|
rb_objspace_t *objspace = &rb_objspace;
|
|
@@ -10239,22 +10251,70 @@ heap_check_moved_i(void *vstart, void *vend, size_t stride, void *data)
|
|
return 0;
|
|
}
|
|
|
|
+/*
|
|
+ * call-seq:
|
|
+ * GC.compact
|
|
+ *
|
|
+ * This function compacts objects together in Ruby's heap. It eliminates
|
|
+ * unused space (or fragmentation) in the heap by moving objects in to that
|
|
+ * unused space. This function returns a hash which contains statistics about
|
|
+ * which objects were moved. See `GC.latest_gc_info` for details about
|
|
+ * compaction statistics.
|
|
+ *
|
|
+ * This method is implementation specific and not expected to be implemented
|
|
+ * in any implementation besides MRI.
|
|
+ */
|
|
static VALUE
|
|
-gc_compact(rb_execution_context_t *ec, VALUE self)
|
|
+gc_compact(VALUE self)
|
|
{
|
|
/* Run GC with compaction enabled */
|
|
- gc_start_internal(ec, self, Qtrue, Qtrue, Qtrue, Qtrue);
|
|
+ gc_start_internal(NULL, self, Qtrue, Qtrue, Qtrue, Qtrue);
|
|
|
|
- return gc_compact_stats(ec, self);
|
|
+ return gc_compact_stats(self);
|
|
}
|
|
|
|
+/*
|
|
+ * call-seq:
|
|
+ * GC.verify_compaction_references(toward: nil, double_heap: false) -> hash
|
|
+ *
|
|
+ * Verify compaction reference consistency.
|
|
+ *
|
|
+ * This method is implementation specific. During compaction, objects that
|
|
+ * were moved are replaced with T_MOVED objects. No object should have a
|
|
+ * reference to a T_MOVED object after compaction.
|
|
+ *
|
|
+ * This function doubles the heap to ensure room to move all objects,
|
|
+ * compacts the heap to make sure everything moves, updates all references,
|
|
+ * then performs a full GC. If any object contains a reference to a T_MOVED
|
|
+ * object, that object should be pushed on the mark stack, and will
|
|
+ * make a SEGV.
|
|
+ */
|
|
static VALUE
|
|
-gc_verify_compaction_references(rb_execution_context_t *ec, VALUE self, VALUE double_heap, VALUE toward_empty)
|
|
+gc_verify_compaction_references(int argc, VALUE *argv, VALUE self)
|
|
{
|
|
rb_objspace_t *objspace = &rb_objspace;
|
|
+ VALUE kwargs, double_heap = Qfalse, toward_empty = Qfalse;
|
|
+ static ID id_toward, id_double_heap, id_empty;
|
|
+
|
|
+ if (!id_toward) {
|
|
+ id_toward = rb_intern("toward");
|
|
+ id_double_heap = rb_intern("double_heap");
|
|
+ id_empty = rb_intern("empty");
|
|
+ }
|
|
+
|
|
+ rb_scan_args(argc, argv, ":", &kwargs);
|
|
+ if (!NIL_P(kwargs)) {
|
|
+ if (rb_hash_has_key(kwargs, ID2SYM(id_toward))) {
|
|
+ VALUE toward = rb_hash_aref(kwargs, ID2SYM(id_toward));
|
|
+ toward_empty = (toward == ID2SYM(id_empty)) ? Qtrue : Qfalse;
|
|
+ }
|
|
+ if (rb_hash_has_key(kwargs, ID2SYM(id_double_heap))) {
|
|
+ double_heap = rb_hash_aref(kwargs, ID2SYM(id_double_heap));
|
|
+ }
|
|
+ }
|
|
|
|
/* Clear the heap. */
|
|
- gc_start_internal(ec, self, Qtrue, Qtrue, Qtrue, Qfalse);
|
|
+ gc_start_internal(NULL, self, Qtrue, Qtrue, Qtrue, Qfalse);
|
|
|
|
RB_VM_LOCK_ENTER();
|
|
{
|
|
@@ -10274,12 +10334,12 @@ gc_verify_compaction_references(rb_execution_context_t *ec, VALUE self, VALUE do
|
|
}
|
|
RB_VM_LOCK_LEAVE();
|
|
|
|
- gc_start_internal(ec, self, Qtrue, Qtrue, Qtrue, Qtrue);
|
|
+ gc_start_internal(NULL, self, Qtrue, Qtrue, Qtrue, Qtrue);
|
|
|
|
objspace_reachable_objects_from_root(objspace, root_obj_check_moved_i, NULL);
|
|
objspace_each_objects(objspace, heap_check_moved_i, NULL, TRUE);
|
|
|
|
- return gc_compact_stats(ec, self);
|
|
+ return gc_compact_stats(self);
|
|
}
|
|
|
|
VALUE
|
|
@@ -10740,8 +10800,18 @@ gc_disable(rb_execution_context_t *ec, VALUE _)
|
|
return rb_gc_disable();
|
|
}
|
|
|
|
+/*
|
|
+ * call-seq:
|
|
+ * GC.auto_compact = flag
|
|
+ *
|
|
+ * Updates automatic compaction mode.
|
|
+ *
|
|
+ * When enabled, the compactor will execute on every major collection.
|
|
+ *
|
|
+ * Enabling compaction will degrade performance on major collections.
|
|
+ */
|
|
static VALUE
|
|
-gc_set_auto_compact(rb_execution_context_t *ec, VALUE _, VALUE v)
|
|
+gc_set_auto_compact(VALUE _, VALUE v)
|
|
{
|
|
/* If not MinGW, Windows, or does not have mmap, we cannot use mprotect for
|
|
* the read barrier, so we must disable automatic compaction. */
|
|
@@ -10755,8 +10825,14 @@ gc_set_auto_compact(rb_execution_context_t *ec, VALUE _, VALUE v)
|
|
return v;
|
|
}
|
|
|
|
+/*
|
|
+ * call-seq:
|
|
+ * GC.auto_compact -> true or false
|
|
+ *
|
|
+ * Returns whether or not automatic compaction has been enabled.
|
|
+ */
|
|
static VALUE
|
|
-gc_get_auto_compact(rb_execution_context_t *ec, VALUE _)
|
|
+gc_get_auto_compact(VALUE _)
|
|
{
|
|
return RBOOL(ruby_enable_autocompact);
|
|
}
|
|
@@ -13656,6 +13732,11 @@ Init_GC(void)
|
|
rb_define_singleton_method(rb_mGC, "malloc_allocated_size", gc_malloc_allocated_size, 0);
|
|
rb_define_singleton_method(rb_mGC, "malloc_allocations", gc_malloc_allocations, 0);
|
|
#endif
|
|
+ rb_define_singleton_method(rb_mGC, "compact", gc_compact, 0);
|
|
+ rb_define_singleton_method(rb_mGC, "auto_compact", gc_get_auto_compact, 0);
|
|
+ rb_define_singleton_method(rb_mGC, "auto_compact=", gc_set_auto_compact, 1);
|
|
+ rb_define_singleton_method(rb_mGC, "latest_compact_info", gc_compact_stats, 0);
|
|
+ rb_define_singleton_method(rb_mGC, "verify_compaction_references", gc_verify_compaction_references, -1);
|
|
|
|
#if GC_DEBUG_STRESS_TO_CLASS
|
|
rb_define_singleton_method(rb_mGC, "add_stress_to_class", rb_gcdebug_add_stress_to_class, -1);
|
|
diff --git a/gc.rb b/gc.rb
|
|
index 72637f3796..9265dd7b57 100644
|
|
--- a/gc.rb
|
|
+++ b/gc.rb
|
|
@@ -38,27 +38,6 @@ def garbage_collect full_mark: true, immediate_mark: true, immediate_sweep: true
|
|
Primitive.gc_start_internal full_mark, immediate_mark, immediate_sweep, false
|
|
end
|
|
|
|
- # call-seq:
|
|
- # GC.auto_compact -> true or false
|
|
- #
|
|
- # Returns whether or not automatic compaction has been enabled.
|
|
- #
|
|
- def self.auto_compact
|
|
- Primitive.gc_get_auto_compact
|
|
- end
|
|
-
|
|
- # call-seq:
|
|
- # GC.auto_compact = flag
|
|
- #
|
|
- # Updates automatic compaction mode.
|
|
- #
|
|
- # When enabled, the compactor will execute on every major collection.
|
|
- #
|
|
- # Enabling compaction will degrade performance on major collections.
|
|
- def self.auto_compact=(flag)
|
|
- Primitive.gc_set_auto_compact(flag)
|
|
- end
|
|
-
|
|
# call-seq:
|
|
# GC.enable -> true or false
|
|
#
|
|
@@ -210,53 +189,6 @@ def self.latest_gc_info hash_or_key = nil
|
|
Primitive.gc_latest_gc_info hash_or_key
|
|
end
|
|
|
|
- # call-seq:
|
|
- # GC.latest_compact_info -> {:considered=>{:T_CLASS=>11}, :moved=>{:T_CLASS=>11}}
|
|
- #
|
|
- # Returns information about object moved in the most recent GC compaction.
|
|
- #
|
|
- # The returned hash has two keys :considered and :moved. The hash for
|
|
- # :considered lists the number of objects that were considered for movement
|
|
- # by the compactor, and the :moved hash lists the number of objects that
|
|
- # were actually moved. Some objects can't be moved (maybe they were pinned)
|
|
- # so these numbers can be used to calculate compaction efficiency.
|
|
- def self.latest_compact_info
|
|
- Primitive.gc_compact_stats
|
|
- end
|
|
-
|
|
- # call-seq:
|
|
- # GC.compact
|
|
- #
|
|
- # This function compacts objects together in Ruby's heap. It eliminates
|
|
- # unused space (or fragmentation) in the heap by moving objects in to that
|
|
- # unused space. This function returns a hash which contains statistics about
|
|
- # which objects were moved. See `GC.latest_gc_info` for details about
|
|
- # compaction statistics.
|
|
- #
|
|
- # This method is implementation specific and not expected to be implemented
|
|
- # in any implementation besides MRI.
|
|
- def self.compact
|
|
- Primitive.gc_compact
|
|
- end
|
|
-
|
|
- # call-seq:
|
|
- # GC.verify_compaction_references(toward: nil, double_heap: false) -> hash
|
|
- #
|
|
- # Verify compaction reference consistency.
|
|
- #
|
|
- # This method is implementation specific. During compaction, objects that
|
|
- # were moved are replaced with T_MOVED objects. No object should have a
|
|
- # reference to a T_MOVED object after compaction.
|
|
- #
|
|
- # This function doubles the heap to ensure room to move all objects,
|
|
- # compacts the heap to make sure everything moves, updates all references,
|
|
- # then performs a full GC. If any object contains a reference to a T_MOVED
|
|
- # object, that object should be pushed on the mark stack, and will
|
|
- # make a SEGV.
|
|
- def self.verify_compaction_references(toward: nil, double_heap: false)
|
|
- Primitive.gc_verify_compaction_references(double_heap, toward == :empty)
|
|
- end
|
|
-
|
|
# call-seq:
|
|
# GC.using_rvargc? -> true or false
|
|
#
|
|
|
|
From d3273559356db6852d1fd794f0f076fba100e09e Mon Sep 17 00:00:00 2001
|
|
From: Mike Dalessio <mike.dalessio@gmail.com>
|
|
Date: Mon, 23 May 2022 17:31:14 -0400
|
|
Subject: [PATCH 2/2] Define unsupported GC compaction methods as
|
|
rb_f_notimplement
|
|
|
|
Fixes [Bug #18779]
|
|
|
|
Define the following methods as `rb_f_notimplement` on unsupported
|
|
platforms:
|
|
|
|
- GC.compact
|
|
- GC.auto_compact
|
|
- GC.auto_compact=
|
|
- GC.latest_compact_info
|
|
- GC.verify_compaction_references
|
|
|
|
This change allows users to call `GC.respond_to?(:compact)` to
|
|
properly test for compaction support. Previously, it was necessary to
|
|
invoke `GC.compact` or `GC.verify_compaction_references` and check if
|
|
those methods raised `NotImplementedError` to determine if compaction
|
|
was supported.
|
|
|
|
This follows the precedent set for other platform-specific
|
|
methods. For example, in `process.c` for methods such as
|
|
`Process.fork`, `Process.setpgid`, and `Process.getpriority`.
|
|
---
|
|
gc.c | 31 +++++++++++++++----
|
|
test/ruby/test_gc_compact.rb | 58 ++++++++++++++++++++++++++----------
|
|
2 files changed, 69 insertions(+), 20 deletions(-)
|
|
|
|
diff --git a/gc.c b/gc.c
|
|
index 92ed76cf96..d71924846a 100644
|
|
--- a/gc.c
|
|
+++ b/gc.c
|
|
@@ -9439,6 +9439,7 @@ gc_move(rb_objspace_t *objspace, VALUE scan, VALUE free, size_t slot_size)
|
|
return (VALUE)src;
|
|
}
|
|
|
|
+#if GC_COMPACTION_SUPPORTED
|
|
static int
|
|
compare_free_slots(const void *left, const void *right, void *dummy)
|
|
{
|
|
@@ -9486,6 +9487,7 @@ gc_sort_heap_by_empty_slots(rb_objspace_t *objspace)
|
|
free(page_list);
|
|
}
|
|
}
|
|
+#endif
|
|
|
|
static void
|
|
gc_ref_update_array(rb_objspace_t * objspace, VALUE v)
|
|
@@ -10165,6 +10167,7 @@ gc_update_references(rb_objspace_t *objspace)
|
|
gc_update_table_refs(objspace, finalizer_table);
|
|
}
|
|
|
|
+#if GC_COMPACTION_SUPPORTED
|
|
/*
|
|
* call-seq:
|
|
* GC.latest_compact_info -> {:considered=>{:T_CLASS=>11}, :moved=>{:T_CLASS=>11}}
|
|
@@ -10201,7 +10204,11 @@ gc_compact_stats(VALUE self)
|
|
|
|
return h;
|
|
}
|
|
+#else
|
|
+# define gc_compact_stats rb_f_notimplement
|
|
+#endif
|
|
|
|
+#if GC_COMPACTION_SUPPORTED
|
|
static void
|
|
root_obj_check_moved_i(const char *category, VALUE obj, void *data)
|
|
{
|
|
@@ -10263,6 +10270,10 @@ heap_check_moved_i(void *vstart, void *vend, size_t stride, void *data)
|
|
*
|
|
* This method is implementation specific and not expected to be implemented
|
|
* in any implementation besides MRI.
|
|
+ *
|
|
+ * To test whether GC compaction is supported, use the idiom:
|
|
+ *
|
|
+ * GC.respond_to?(:compact)
|
|
*/
|
|
static VALUE
|
|
gc_compact(VALUE self)
|
|
@@ -10272,7 +10283,11 @@ gc_compact(VALUE self)
|
|
|
|
return gc_compact_stats(self);
|
|
}
|
|
+#else
|
|
+# define gc_compact rb_f_notimplement
|
|
+#endif
|
|
|
|
+#if GC_COMPACTION_SUPPORTED
|
|
/*
|
|
* call-seq:
|
|
* GC.verify_compaction_references(toward: nil, double_heap: false) -> hash
|
|
@@ -10341,6 +10356,9 @@ gc_verify_compaction_references(int argc, VALUE *argv, VALUE self)
|
|
|
|
return gc_compact_stats(self);
|
|
}
|
|
+#else
|
|
+# define gc_verify_compaction_references rb_f_notimplement
|
|
+#endif
|
|
|
|
VALUE
|
|
rb_gc_start(void)
|
|
@@ -10800,6 +10818,7 @@ gc_disable(rb_execution_context_t *ec, VALUE _)
|
|
return rb_gc_disable();
|
|
}
|
|
|
|
+#if GC_COMPACTION_SUPPORTED
|
|
/*
|
|
* call-seq:
|
|
* GC.auto_compact = flag
|
|
@@ -10815,16 +10834,15 @@ gc_set_auto_compact(VALUE _, VALUE v)
|
|
{
|
|
/* If not MinGW, Windows, or does not have mmap, we cannot use mprotect for
|
|
* the read barrier, so we must disable automatic compaction. */
|
|
-#if !defined(__MINGW32__) && !defined(_WIN32)
|
|
- if (!USE_MMAP_ALIGNED_ALLOC) {
|
|
- rb_raise(rb_eNotImpError, "Automatic compaction isn't available on this platform");
|
|
- }
|
|
-#endif
|
|
|
|
ruby_enable_autocompact = RTEST(v);
|
|
return v;
|
|
}
|
|
+#else
|
|
+# define gc_set_auto_compact rb_f_notimplement
|
|
+#endif
|
|
|
|
+#if GC_COMPACTION_SUPPORTED
|
|
/*
|
|
* call-seq:
|
|
* GC.auto_compact -> true or false
|
|
@@ -10836,6 +10854,9 @@ gc_get_auto_compact(VALUE _)
|
|
{
|
|
return RBOOL(ruby_enable_autocompact);
|
|
}
|
|
+#else
|
|
+# define gc_get_auto_compact rb_f_notimplement
|
|
+#endif
|
|
|
|
static int
|
|
get_envparam_size(const char *name, size_t *default_value, size_t lower_bound)
|
|
diff --git a/test/ruby/test_gc_compact.rb b/test/ruby/test_gc_compact.rb
|
|
index 42ad028530..411d5eab69 100644
|
|
--- a/test/ruby/test_gc_compact.rb
|
|
+++ b/test/ruby/test_gc_compact.rb
|
|
@@ -9,14 +9,7 @@
|
|
end
|
|
|
|
class TestGCCompact < Test::Unit::TestCase
|
|
- module SupportsCompact
|
|
- def setup
|
|
- skip "autocompact not supported on this platform" unless supports_auto_compact?
|
|
- super
|
|
- end
|
|
-
|
|
- private
|
|
-
|
|
+ module CompactionSupportInspector
|
|
def supports_auto_compact?
|
|
return true unless defined?(Etc::SC_PAGE_SIZE)
|
|
|
|
@@ -30,10 +23,19 @@ def supports_auto_compact?
|
|
end
|
|
end
|
|
|
|
- include SupportsCompact
|
|
+ module OmitUnlessCompactSupported
|
|
+ include CompactionSupportInspector
|
|
+
|
|
+ def setup
|
|
+ omit "autocompact not supported on this platform" unless supports_auto_compact?
|
|
+ super
|
|
+ end
|
|
+ end
|
|
+
|
|
+ include OmitUnlessCompactSupported
|
|
|
|
class AutoCompact < Test::Unit::TestCase
|
|
- include SupportsCompact
|
|
+ include OmitUnlessCompactSupported
|
|
|
|
def test_enable_autocompact
|
|
before = GC.auto_compact
|
|
@@ -87,13 +89,39 @@ def test_implicit_compaction_does_something
|
|
end
|
|
end
|
|
|
|
- def os_page_size
|
|
- return true unless defined?(Etc::SC_PAGE_SIZE)
|
|
+ class CompactMethodsNotImplemented < Test::Unit::TestCase
|
|
+ include CompactionSupportInspector
|
|
+
|
|
+ def assert_not_implemented(method, *args)
|
|
+ omit "autocompact is supported on this platform" if supports_auto_compact?
|
|
+
|
|
+ assert_raise(NotImplementedError) { GC.send(method, *args) }
|
|
+ refute(GC.respond_to?(method), "GC.#{method} should be defined as rb_f_notimplement")
|
|
+ end
|
|
+
|
|
+ def test_gc_compact_not_implemented
|
|
+ assert_not_implemented(:compact)
|
|
+ end
|
|
+
|
|
+ def test_gc_auto_compact_get_not_implemented
|
|
+ assert_not_implemented(:auto_compact)
|
|
+ end
|
|
+
|
|
+ def test_gc_auto_compact_set_not_implemented
|
|
+ assert_not_implemented(:auto_compact=, true)
|
|
+ end
|
|
+
|
|
+ def test_gc_latest_compact_info_not_implemented
|
|
+ assert_not_implemented(:latest_compact_info)
|
|
+ end
|
|
+
|
|
+ def test_gc_verify_compaction_references_not_implemented
|
|
+ assert_not_implemented(:verify_compaction_references)
|
|
+ end
|
|
end
|
|
|
|
- def setup
|
|
- skip "autocompact not supported on this platform" unless supports_auto_compact?
|
|
- super
|
|
+ def os_page_size
|
|
+ return true unless defined?(Etc::SC_PAGE_SIZE)
|
|
end
|
|
|
|
def test_gc_compact_stats
|