Skip to content

Infinite loop in StringScanner regex #7649

@scottmtp

Description

@scottmtp

The script below causes an infinite loop in JRuby. The regex that causes the error is from Rack's multipart parser and we first noticed the problem there. The script hangs when run in jruby 9.3.10.0 and jruby 9.4.1.0, and it does not hang when running cruby 2.7.6.

Environment Information

I've been able to reproduce the problem on my Mac and a Docker container running Ubuntu

Linux:

uname -a
Linux 5eae50c2107a 4.15.0-197-generic #208-Ubuntu SMP Tue Nov 1 17:23:37 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

jruby -v
jruby 9.3.10.0 (2.6.8) 2023-02-01 107b2e6697 OpenJDK 64-Bit Server VM 25.352-b08 on 1.8.0_352-8u352-ga-1~22.04-b08 +jit [x86_64-linux]

Mac:

jruby -v                                                                                                                                                                    ✘ 130 
  (3.1.0) 2023-02-07 237d5fa5f4 OpenJDK 64-Bit Server VM 25.352-b00 on 1.8.0_352-bre_2022_11_08_21_10-b00 +jit [x86_64-darwin]

uname -a
Darwin scott.domain 21.6.0 Darwin Kernel Version 21.6.0: Sun Nov  6 23:31:16 PST 2022; root:xnu-8020.240.14~1/RELEASE_X86_64 x86_64 i386 Darwin

Test Script:

require 'base64'
require 'strscan'

encoded = <<-TEXT
LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLTU2OTQ1Mjk5NDEwMzkwNzQxODYzMTk2Nw0KQ29u
dGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJ0ZW1wbGF0ZXMiDQoNClxu4oCDLS0t
LS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLTU2
OTQ1Mjk5NDEwMzkwNzQxODYzMTk2Ny0tDQo=
TEXT

request_body = Base64.decode64(encoded)

EOL = "\r\n"
boundary = "----------------------------569452994103907418631967"

regex = /(?:#{EOL})?#{Regexp.quote(boundary)}(?:#{EOL}|--)/m

scanner = StringScanner.new("".dup)
scanner.concat request_body

puts 'first'
if body_with_boundary = scanner.check_until(regex) # check but do not advance the pointer yet
  body = body_with_boundary.sub(/#{@body_regex}\z/m, '') # remove the boundary from the string
  scanner.pos += body.length + 2 # skip \r\n after the content
  puts scanner.pos
  puts body
end

puts 'second'
if body_with_boundary = scanner.check_until(regex) # check but do not advance the pointer yet
  body = body_with_boundary.sub(/#{@body_regex}\z/m, '') # remove the boundary from the string
  scanner.pos += body.length + 2 # skip \r\n after the content
  puts scanner.pos
  puts body
end

puts 'DONE'

Expected Behavior

The script exits normally.

Actual Behavior

The script hangs here:

"main" #1 prio=5 os_prio=31 tid=0x00007fa086008800 nid=0x1a03 runnable [0x000070000be5b000]
   java.lang.Thread.State: RUNNABLE
	at org.joni.ByteCodeMachine.executeSb(ByteCodeMachine.java:322)
	at org.joni.ByteCodeMachine.matchAt(ByteCodeMachine.java:169)
	at org.joni.Matcher.matchCheck(Matcher.java:289)
	at org.joni.Matcher.searchCommon(Matcher.java:453)
	at org.joni.Matcher.searchInterruptible(Matcher.java:318)
	at org.jruby.RubyRegexp$SearchMatchTask.run(RubyRegexp.java:284)
	at org.jruby.RubyRegexp$SearchMatchTask.run(RubyRegexp.java:265)
	at org.jruby.RubyThread.executeTask(RubyThread.java:1712)
	at org.jruby.RubyThread.executeTask(RubyThread.java:1690)
	at org.jruby.RubyRegexp.matcherSearch(RubyRegexp.java:229)
	at org.jruby.ext.strscan.RubyStringScanner.scan(RubyStringScanner.java:277)
	at org.jruby.ext.strscan.RubyStringScanner.check_until(RubyStringScanner.java:339)
	at org.jruby.ext.strscan.RubyStringScanner$INVOKER$i$1$0$check_until.call(RubyStringScanner$INVOKER$i$1$0$check_until.gen)
	at org.jruby.runtime.callsite.CachingCallSite.cacheAndCall(CachingCallSite.java:372)
	at org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:175)
	at $_dot_.jruby_minus_regex.invokeOther20:check_until(./jruby-regex.rb:30)
	at $_dot_.jruby_minus_regex.RUBY$script(./jruby-regex.rb:30)
	at $_dot_.jruby_minus_regex.run(./jruby-regex.rb)
	at java.lang.invoke.LambdaForm$DMH/1714078840.invokeStatic_L3I_L(LambdaForm$DMH)
	at java.lang.invoke.LambdaForm$BMH/989892772.reinvoke(LambdaForm$BMH)
	at java.lang.invoke.LambdaForm$MH/1051876890.invoker(LambdaForm$MH)
	at java.lang.invoke.LambdaForm$MH/551734240.invokeExact_MT(LambdaForm$MH)
	at java.lang.invoke.MethodHandle.invokeWithArguments(MethodHandle.java:627)
	at org.jruby.ir.Compiler$1.load(Compiler.java:114)
	at org.jruby.Ruby.runScript(Ruby.java:1259)
	at org.jruby.Ruby.runNormally(Ruby.java:1178)
	at org.jruby.Ruby.runNormally(Ruby.java:1160)
	at org.jruby.Ruby.runNormally(Ruby.java:1196)
	at org.jruby.Ruby.runFromMain(Ruby.java:977)
	at org.jruby.Main.doRunFromMain(Main.java:408)
	at org.jruby.Main.internalRun(Main.java:292)
	at org.jruby.Main.run(Main.java:237)
	at org.jruby.Main.main(Main.java:209)

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions