VYPR
Medium severity4.5NVD Advisory· Published May 14, 2024· Updated Apr 15, 2026

CVE-2024-27281

CVE-2024-27281

Description

An issue was discovered in RDoc 6.3.3 through 6.6.2, as distributed in Ruby 3.x through 3.3.0. When parsing .rdoc_options (used for configuration in RDoc) as a YAML file, object injection and resultant remote code execution are possible because there are no restrictions on the classes that can be restored. (When loading the documentation cache, object injection and resultant remote code execution are also possible if there were a crafted cache.) The main fixed version is 6.6.3.1. For Ruby 3.0 users, a fixed version is rdoc 6.3.4.1. For Ruby 3.1 users, a fixed version is rdoc 6.4.1.1. For Ruby 3.2 users, a fixed version is rdoc 6.5.1.1.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
rdocRubyGems
>= 6.3.3, < 6.3.4.16.3.4.1
rdocRubyGems
>= 6.4.0, < 6.4.1.16.4.1.1
rdocRubyGems
>= 6.5.0, < 6.5.1.16.5.1.1
rdocRubyGems
>= 6.6.0, < 6.6.3.16.6.3.1

Patches

8
a5de13bf0f0c

Fix NoMethodError for start_with

https://github.com/ruby/rdocHiroshi SHIBATAMar 21, 2024via ghsa
1 file changed · +1 1
  • lib/rdoc/store.rb+1 1 modified
    @@ -975,7 +975,7 @@ def marshal_load(file)
         case obj
         when true, false, nil, Array, Class, Encoding, Hash, Integer, String, Symbol, RDoc::Text
         else
    -      unless obj.class.name.start_with("RDoc::")
    +      unless obj.class.name.start_with?("RDoc::")
             raise TypeError, "not permitted class: #{obj.class.name}"
           end
         end
    
d22ba930f1f6

Fix NoMethodError for start_with

https://github.com/ruby/rdocHiroshi SHIBATAMar 21, 2024via ghsa
1 file changed · +1 1
  • lib/rdoc/store.rb+1 1 modified
    @@ -975,7 +975,7 @@ def marshal_load(file)
         case obj
         when true, false, nil, Array, Class, Encoding, Hash, Integer, String, Symbol, RDoc::Text
         else
    -      unless obj.class.name.start_with("RDoc::")
    +      unless obj.class.name.start_with?("RDoc::")
             raise TypeError, "not permitted class: #{obj.class.name}"
           end
         end
    
e4a0e71e6f10

Fix NoMethodError for start_with

https://github.com/ruby/rdocHiroshi SHIBATAMar 21, 2024via ghsa
1 file changed · +1 1
  • lib/rdoc/store.rb+1 1 modified
    @@ -978,7 +978,7 @@ def marshal_load(file)
         case obj
         when true, false, nil, Array, Class, Encoding, Hash, Integer, String, Symbol, RDoc::Text
         else
    -      unless obj.class.name.start_with("RDoc::")
    +      unless obj.class.name.start_with?("RDoc::")
             raise TypeError, "not permitted class: #{obj.class.name}"
           end
         end
    
48617985e9fb

Fix NoMethodError for start_with

https://github.com/ruby/rdocHiroshi SHIBATAMar 21, 2024via ghsa
1 file changed · +1 1
  • lib/rdoc/store.rb+1 1 modified
    @@ -975,7 +975,7 @@ def marshal_load(file)
         case obj
         when true, false, nil, Array, Class, Encoding, Hash, Integer, String, Symbol, RDoc::Text
         else
    -      unless obj.class.name.start_with("RDoc::")
    +      unless obj.class.name.start_with?("RDoc::")
             raise TypeError, "not permitted class: #{obj.class.name}"
           end
         end
    
32ff6ba0bebd

Filter marshaled objects

https://github.com/ruby/rdocHiroshi SHIBATAFeb 20, 2024via ghsa
1 file changed · +26 19
  • lib/rdoc/store.rb+26 19 modified
    @@ -556,9 +556,7 @@ def load_all
       def load_cache
         #orig_enc = @encoding
     
    -    File.open cache_path, 'rb' do |io|
    -      @cache = Marshal.load io.read
    -    end
    +    @cache = marshal_load(cache_path)
     
         load_enc = @cache[:encoding]
     
    @@ -615,9 +613,7 @@ def load_class klass_name
       def load_class_data klass_name
         file = class_file klass_name
     
    -    File.open file, 'rb' do |io|
    -      Marshal.load io.read
    -    end
    +    marshal_load(file)
       rescue Errno::ENOENT => e
         error = MissingFileError.new(self, file, klass_name)
         error.set_backtrace e.backtrace
    @@ -630,14 +626,10 @@ def load_class_data klass_name
       def load_method klass_name, method_name
         file = method_file klass_name, method_name
     
    -    File.open file, 'rb' do |io|
    -      obj = Marshal.load io.read
    -      obj.store = self
    -      obj.parent =
    -        find_class_or_module(klass_name) || load_class(klass_name) unless
    -          obj.parent
    -      obj
    -    end
    +    obj = marshal_load(file)
    +    obj.store = self
    +    obj.parent ||= find_class_or_module(klass_name) || load_class(klass_name)
    +    obj
       rescue Errno::ENOENT => e
         error = MissingFileError.new(self, file, klass_name + method_name)
         error.set_backtrace e.backtrace
    @@ -650,11 +642,9 @@ def load_method klass_name, method_name
       def load_page page_name
         file = page_file page_name
     
    -    File.open file, 'rb' do |io|
    -      obj = Marshal.load io.read
    -      obj.store = self
    -      obj
    -    end
    +    obj = marshal_load(file)
    +    obj.store = self
    +    obj
       rescue Errno::ENOENT => e
         error = MissingFileError.new(self, file, page_name)
         error.set_backtrace e.backtrace
    @@ -976,4 +966,21 @@ def unique_modules
         @unique_modules
       end
     
    +  private
    +  def marshal_load(file)
    +    File.open(file, 'rb') {|io| Marshal.load(io, MarshalFilter)}
    +  end
    +
    +  MarshalFilter = proc do |obj|
    +    case obj
    +    when true, false, nil, Array, Class, Encoding, Hash, Integer, String, Symbol, RDoc::Text
    +    else
    +      unless obj.class.name.start_with("RDoc::")
    +        raise TypeError, "not permitted class: #{obj.class.name}"
    +      end
    +    end
    +    obj
    +  end
    +  private_constant :MarshalFilter
    +
     end
    
1254b0066f31

Filter marshaled objects

https://github.com/ruby/rdocHiroshi SHIBATAFeb 20, 2024via ghsa
1 file changed · +26 19
  • lib/rdoc/store.rb+26 19 modified
    @@ -556,9 +556,7 @@ def load_all
       def load_cache
         #orig_enc = @encoding
     
    -    File.open cache_path, 'rb' do |io|
    -      @cache = Marshal.load io.read
    -    end
    +    @cache = marshal_load(cache_path)
     
         load_enc = @cache[:encoding]
     
    @@ -615,9 +613,7 @@ def load_class klass_name
       def load_class_data klass_name
         file = class_file klass_name
     
    -    File.open file, 'rb' do |io|
    -      Marshal.load io.read
    -    end
    +    marshal_load(file)
       rescue Errno::ENOENT => e
         error = MissingFileError.new(self, file, klass_name)
         error.set_backtrace e.backtrace
    @@ -630,14 +626,10 @@ def load_class_data klass_name
       def load_method klass_name, method_name
         file = method_file klass_name, method_name
     
    -    File.open file, 'rb' do |io|
    -      obj = Marshal.load io.read
    -      obj.store = self
    -      obj.parent =
    -        find_class_or_module(klass_name) || load_class(klass_name) unless
    -          obj.parent
    -      obj
    -    end
    +    obj = marshal_load(file)
    +    obj.store = self
    +    obj.parent ||= find_class_or_module(klass_name) || load_class(klass_name)
    +    obj
       rescue Errno::ENOENT => e
         error = MissingFileError.new(self, file, klass_name + method_name)
         error.set_backtrace e.backtrace
    @@ -650,11 +642,9 @@ def load_method klass_name, method_name
       def load_page page_name
         file = page_file page_name
     
    -    File.open file, 'rb' do |io|
    -      obj = Marshal.load io.read
    -      obj.store = self
    -      obj
    -    end
    +    obj = marshal_load(file)
    +    obj.store = self
    +    obj
       rescue Errno::ENOENT => e
         error = MissingFileError.new(self, file, page_name)
         error.set_backtrace e.backtrace
    @@ -976,4 +966,21 @@ def unique_modules
         @unique_modules
       end
     
    +  private
    +  def marshal_load(file)
    +    File.open(file, 'rb') {|io| Marshal.load(io, MarshalFilter)}
    +  end
    +
    +  MarshalFilter = proc do |obj|
    +    case obj
    +    when true, false, nil, Array, Class, Encoding, Hash, Integer, String, Symbol, RDoc::Text
    +    else
    +      unless obj.class.name.start_with("RDoc::")
    +        raise TypeError, "not permitted class: #{obj.class.name}"
    +      end
    +    end
    +    obj
    +  end
    +  private_constant :MarshalFilter
    +
     end
    
da7a0c7553ef

Filter marshaled objets

https://github.com/ruby/rdocNobuyoshi NakadaMar 28, 2022via ghsa
1 file changed · +26 19
  • lib/rdoc/store.rb+26 19 modified
    @@ -559,9 +559,7 @@ def load_all
       def load_cache
         #orig_enc = @encoding
     
    -    File.open cache_path, 'rb' do |io|
    -      @cache = Marshal.load io
    -    end
    +    @cache = marshal_load(cache_path)
     
         load_enc = @cache[:encoding]
     
    @@ -618,9 +616,7 @@ def load_class klass_name
       def load_class_data klass_name
         file = class_file klass_name
     
    -    File.open file, 'rb' do |io|
    -      Marshal.load io
    -    end
    +    marshal_load(file)
       rescue Errno::ENOENT => e
         error = MissingFileError.new(self, file, klass_name)
         error.set_backtrace e.backtrace
    @@ -633,14 +629,10 @@ def load_class_data klass_name
       def load_method klass_name, method_name
         file = method_file klass_name, method_name
     
    -    File.open file, 'rb' do |io|
    -      obj = Marshal.load io
    -      obj.store = self
    -      obj.parent =
    -        find_class_or_module(klass_name) || load_class(klass_name) unless
    -          obj.parent
    -      obj
    -    end
    +    obj = marshal_load(file)
    +    obj.store = self
    +    obj.parent ||= find_class_or_module(klass_name) || load_class(klass_name)
    +    obj
       rescue Errno::ENOENT => e
         error = MissingFileError.new(self, file, klass_name + method_name)
         error.set_backtrace e.backtrace
    @@ -653,11 +645,9 @@ def load_method klass_name, method_name
       def load_page page_name
         file = page_file page_name
     
    -    File.open file, 'rb' do |io|
    -      obj = Marshal.load io
    -      obj.store = self
    -      obj
    -    end
    +    obj = marshal_load(file)
    +    obj.store = self
    +    obj
       rescue Errno::ENOENT => e
         error = MissingFileError.new(self, file, page_name)
         error.set_backtrace e.backtrace
    @@ -979,4 +969,21 @@ def unique_modules
         @unique_modules
       end
     
    +  private
    +  def marshal_load(file)
    +    File.open(file, 'rb') {|io| Marshal.load(io, MarshalFilter)}
    +  end
    +
    +  MarshalFilter = proc do |obj|
    +    case obj
    +    when true, false, nil, Array, Class, Encoding, Hash, Integer, String, Symbol, RDoc::Text
    +    else
    +      unless obj.class.name.start_with("RDoc::")
    +        raise TypeError, "not permitted class: #{obj.class.name}"
    +      end
    +    end
    +    obj
    +  end
    +  private_constant :MarshalFilter
    +
     end
    
811f125a4a0c

Filter marshaled objets

https://github.com/ruby/rdocNobuyoshi NakadaMar 28, 2022via ghsa
1 file changed · +26 19
  • lib/rdoc/store.rb+26 19 modified
    @@ -556,9 +556,7 @@ def load_all
       def load_cache
         #orig_enc = @encoding
     
    -    File.open cache_path, 'rb' do |io|
    -      @cache = Marshal.load io
    -    end
    +    @cache = marshal_load(cache_path)
     
         load_enc = @cache[:encoding]
     
    @@ -615,9 +613,7 @@ def load_class klass_name
       def load_class_data klass_name
         file = class_file klass_name
     
    -    File.open file, 'rb' do |io|
    -      Marshal.load io
    -    end
    +    marshal_load(file)
       rescue Errno::ENOENT => e
         error = MissingFileError.new(self, file, klass_name)
         error.set_backtrace e.backtrace
    @@ -630,14 +626,10 @@ def load_class_data klass_name
       def load_method klass_name, method_name
         file = method_file klass_name, method_name
     
    -    File.open file, 'rb' do |io|
    -      obj = Marshal.load io
    -      obj.store = self
    -      obj.parent =
    -        find_class_or_module(klass_name) || load_class(klass_name) unless
    -          obj.parent
    -      obj
    -    end
    +    obj = marshal_load(file)
    +    obj.store = self
    +    obj.parent ||= find_class_or_module(klass_name) || load_class(klass_name)
    +    obj
       rescue Errno::ENOENT => e
         error = MissingFileError.new(self, file, klass_name + method_name)
         error.set_backtrace e.backtrace
    @@ -650,11 +642,9 @@ def load_method klass_name, method_name
       def load_page page_name
         file = page_file page_name
     
    -    File.open file, 'rb' do |io|
    -      obj = Marshal.load io
    -      obj.store = self
    -      obj
    -    end
    +    obj = marshal_load(file)
    +    obj.store = self
    +    obj
       rescue Errno::ENOENT => e
         error = MissingFileError.new(self, file, page_name)
         error.set_backtrace e.backtrace
    @@ -976,4 +966,21 @@ def unique_modules
         @unique_modules
       end
     
    +  private
    +  def marshal_load(file)
    +    File.open(file, 'rb') {|io| Marshal.load(io, MarshalFilter)}
    +  end
    +
    +  MarshalFilter = proc do |obj|
    +    case obj
    +    when true, false, nil, Array, Class, Encoding, Hash, Integer, String, Symbol, RDoc::Text
    +    else
    +      unless obj.class.name.start_with("RDoc::")
    +        raise TypeError, "not permitted class: #{obj.class.name}"
    +      end
    +    end
    +    obj
    +  end
    +  private_constant :MarshalFilter
    +
     end
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

19

News mentions

0

No linked articles in our index yet.