From d1589536d2b1cf7056cdf1d11e61db268b74a6ca Mon Sep 17 00:00:00 2001
From: Takashi Kokubun <takashikkbn@gmail.com>
Date: Tue, 21 Apr 2026 16:27:44 +0900
Subject: [PATCH] [ruby/erb] Prohibit def_method on marshal-loaded ERB
 instances

Extends the @_init guard to def_method so that an ERB object created
via Marshal.load (which bypasses initialize) raises ArgumentError
instead of evaluating arbitrary source. def_module and def_class both
delegate to def_method and are covered by the same check.

https://github.com/ruby/erb/commit/9d017be4e3

Co-authored-by: Tristan Madani <TristanInSec@gmail.com>
---
 lib/erb.rb           |  3 +++
 test/erb/test_erb.rb | 27 +++++++++++++++++++++++++++
 2 files changed, 30 insertions(+)

diff --git a/lib/erb.rb b/lib/erb.rb
index 754419f..4499227 100644
--- a/lib/erb.rb
+++ b/lib/erb.rb
@@ -463,6 +463,9 @@ class ERB
   #   erb.def_method(MyClass, 'render(arg1, arg2)', filename)
   #   print MyClass.new.render('foo', 123)
   def def_method(mod, methodname, fname='(ERB)')
+    unless @_init.equal?(self.class.singleton_class)
+      raise ArgumentError, "not initialized"
+    end
     src = self.src.sub(/^(?!#|$)/) {"def #{methodname}\n"} << "\nend\n"
     mod.module_eval do
       eval(src, binding, fname, -1)
diff --git a/test/erb/test_erb.rb b/test/erb/test_erb.rb
index 555345a..1266b64 100644
--- a/test/erb/test_erb.rb
+++ b/test/erb/test_erb.rb
@@ -714,6 +714,33 @@ EOS
     assert_raise(ArgumentError) {erb.result}
   end
 
+  def test_prohibited_marshal_load_def_method
+    erb = ERB.allocate
+    erb.instance_variable_set(:@src, "")
+    erb.instance_variable_set(:@lineno, 1)
+    erb.instance_variable_set(:@_init, true)
+    erb = Marshal.load(Marshal.dump(erb))
+    assert_raise(ArgumentError) {erb.def_method(Class.new, 'render')}
+  end
+
+  def test_prohibited_marshal_load_def_module
+    erb = ERB.allocate
+    erb.instance_variable_set(:@src, "")
+    erb.instance_variable_set(:@lineno, 1)
+    erb.instance_variable_set(:@_init, true)
+    erb = Marshal.load(Marshal.dump(erb))
+    assert_raise(ArgumentError) {erb.def_module}
+  end
+
+  def test_prohibited_marshal_load_def_class
+    erb = ERB.allocate
+    erb.instance_variable_set(:@src, "")
+    erb.instance_variable_set(:@lineno, 1)
+    erb.instance_variable_set(:@_init, true)
+    erb = Marshal.load(Marshal.dump(erb))
+    assert_raise(ArgumentError) {erb.def_class}
+  end
+
   def test_multi_line_comment_lineno
     erb = ERB.new(<<~EOS)
       <%= __LINE__ %>
-- 
2.43.0