Reject relabeling a module via TYPE_UCLASS in Marshal.load (#19115)

This is not a real-world problem. Marshal.dump never emits this payload.
It can only be hand-crafted. Loading untrusted Marshal data is already
unsafe. This fix only avoids false positives from security scanners.

TYPE_UCLASS ('C') relabels the wrapped object's class with RBASIC_SET_CLASS.
It assumes the object was freshly allocated during load. But a wrapped
module is not. The read returns the live, shared module object. So the
relabel hits that global object instead.

Classes were already rejected here. Now reject modules too. This restores
the behavior before 959d5febcf.
This commit is contained in:
Sutou Kouhei authored and GitHub committed 2026-09-30 05:57:48 +09:00
1 parent 32154539ad
commit ea28d3839b
2 files changed
+15 -2
Notes: git 2026-09-29 20:58:21 +00:00
Merged-By: kou <kou@clear-code.com>

No files matched your search

+2 -2
View File
@@ -2013,10 +2013,10 @@ r_object_for(struct load_arg *arg, bool partial, int *ivp, VALUE klass, VALUE ex
goto type_hash;
}
v = r_object_for(arg, partial, 0, c, extmod, type);
if (RB_SPECIAL_CONST_P(v) || RB_TYPE_P(v, T_OBJECT) || RB_TYPE_P(v, T_CLASS)) {
if (RB_SPECIAL_CONST_P(v) || RB_TYPE_P(v, T_OBJECT) || RB_TYPE_P(v, T_CLASS) || RB_TYPE_P(v, T_MODULE)) {
goto format_error;
}
if (RB_TYPE_P(v, T_MODULE) || !RTEST(rb_class_inherited_p(c, RBASIC(v)->klass))) {
if (!RTEST(rb_class_inherited_p(c, RBASIC(v)->klass))) {
VALUE tmp = rb_obj_alloc(c);
if (TYPE(v) != TYPE(tmp)) goto format_error;
+13
View File
@@ -169,6 +169,19 @@ class TestMarshal < Test::Unit::TestCase
assert_equal(Enumerable, Marshal.load(Marshal.dump(Enumerable)))
end
class ModuleSubclass < Module
end
def test_uclass_module_not_relabeled
# TYPE_UCLASS ('C') wrapping a module must be rejected, not relabel it.
uclass = Marshal.dump(ModuleSubclass.name.to_sym)[2..-1] # ":<name>"
wrapped = Marshal.dump(TestModule)[2..-1] # "m<name>"
payload = "\x04\x08C#{uclass}#{wrapped}"
assert_raise_with_message(ArgumentError, "dump format error (user class)") do
Marshal.load(payload)
end
end
class C2
def initialize(ary)
@ary = ary