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.
| Package | Affected versions | Patched versions |
|---|---|---|
rdocRubyGems | >= 6.3.3, < 6.3.4.1 | 6.3.4.1 |
rdocRubyGems | >= 6.4.0, < 6.4.1.1 | 6.4.1.1 |
rdocRubyGems | >= 6.5.0, < 6.5.1.1 | 6.5.1.1 |
rdocRubyGems | >= 6.6.0, < 6.6.3.1 | 6.6.3.1 |
Patches
8a5de13bf0f0cFix NoMethodError for start_with
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
d22ba930f1f6Fix NoMethodError for start_with
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
e4a0e71e6f10Fix NoMethodError for start_with
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
48617985e9fbFix NoMethodError for start_with
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
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
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
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
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- github.com/advisories/GHSA-592j-995h-p23jghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-27281ghsaADVISORY
- github.com/ruby/rdoc/commit/1254b0066f312ddbf7fae7a195e66ce5b3bc6656ghsaWEB
- github.com/ruby/rdoc/commit/32ff6ba0bebd8ea26f569da5fd23be2937f6a644ghsaWEB
- github.com/ruby/rdoc/commit/48617985e9fbc2825219d55f04e3e0e98d2923beghsaWEB
- github.com/ruby/rdoc/commit/811f125a4a0cc968e3eb18e16ea6c1a3b49a11bfghsaWEB
- github.com/ruby/rdoc/commit/a5de13bf0f0c26f8e764e82b5bf4bf8bffc7198eghsaWEB
- github.com/ruby/rdoc/commit/d22ba930f1f611dda531dba04cd3d2531bb3f8a5ghsaWEB
- github.com/ruby/rdoc/commit/da7a0c7553ef7250ca665a3fecdc01dbaacbb43dghsaWEB
- github.com/ruby/rdoc/commit/e4a0e71e6f1032f8b4e5e58b4ef60d702c22ce17ghsaWEB
- github.com/rubysec/ruby-advisory-db/blob/master/gems/rdoc/CVE-2024-27281.ymlghsaWEB
- hackerone.com/reports/1187477nvdWEB
- lists.debian.org/debian-lts-announce/2024/09/msg00000.htmlnvdWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/27LUWREIFTP3MQAW7QE4PJM4DPAQJWXFghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/XYDHPHEZI7OQXTQKTDZHGZNPIJH7ZV5NghsaWEB
- www.ruby-lang.org/en/news/2024/03/21/rce-rdoc-cve-2024-27281ghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/27LUWREIFTP3MQAW7QE4PJM4DPAQJWXF/nvd
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/XYDHPHEZI7OQXTQKTDZHGZNPIJH7ZV5N/nvd
- www.ruby-lang.org/en/news/2024/03/21/rce-rdoc-cve-2024-27281/nvd
News mentions
0No linked articles in our index yet.