Skip to content

Commit d9ee837

Browse files
committed
Merge pull request rubychan#149 from rubychan/fix-cache-attack
Fix Symbol/Cache attacks
2 parents 21d07b3 + 28c57a5 commit d9ee837

File tree

8 files changed

+74
-46
lines changed

8 files changed

+74
-46
lines changed

Changes.textile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,14 @@ h2. Changes in 1.1
2424
* New token type @:id@ for CSS/Sass [#27]
2525
* New token type @:done@ for Taskpaper [#39]
2626
* New token type @:map@ for Lua, introducing a nice nested-shades trick [#22, thanks to Quintus and Nathan Youngman]
27+
* New token type @:unknown@ for Debug scanner
2728
* Display line numbers in HTML @:table@ mode even for single-line code (remove special case) [#41, thanks to Ariejan de Vroom]
2829
* Override Bootstrap's @pre { word-break: break-all }@ styling for line numbers [#102, thanks to lightswitch05]
2930
* Fixed @:docstring@ token type style
31+
* Fixed several problems related to Hash caches and dynamic Symbol creation that might have been exploited by an attacker [#148]
32+
* @PluginHost@ now works with Strings instead of Symbols internally (to avoid using @#to_sym@)
33+
* The @Debug@ scanner maps unknown token kinds to @:unknown@ (to avoid creating Symbols based on possibly unsafe input)
34+
* The @Raydebug@ scanner highlights unknown token kinds as @:plain@
3035
* @Plugin@ does not warn about fallback when default is defined
3136
* @HTML@ encoder will not warn about unclosed token groups at the end of the stream
3237
* @Debug@ encoder refactored; use @DebugLint@ if you want strict checking now

lib/coderay/encoders/html.rb

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ def style_for_kinds kinds
285285

286286
def make_span_for_kinds method, hint
287287
Hash.new do |h, kinds|
288-
h[kinds.is_a?(Symbol) ? kinds : kinds.dup] = begin
288+
begin
289289
css_class = css_class_for_kinds(kinds)
290290
title = HTML.token_path_to_hint hint, kinds if hint
291291

@@ -297,6 +297,9 @@ def make_span_for_kinds method, hint
297297
"<span#{title}#{" class=\"#{css_class}\"" if css_class}>"
298298
end
299299
end
300+
end.tap do |span|
301+
h.clear if h.size >= 100
302+
h[kinds] = span
300303
end
301304
end
302305
end
@@ -309,8 +312,8 @@ def check_group_nesting name, kind
309312

310313
def break_lines text, style
311314
reopen = ''
312-
@opened.each_with_index do |k, index|
313-
reopen << (@span_for_kinds[index > 0 ? [k, *@opened[0...index]] : k] || '<span>')
315+
@opened.each_with_index do |kind, index|
316+
reopen << (@span_for_kinds[index > 0 ? [kind, *@opened[0...index]] : kind] || '<span>')
314317
end
315318
text.gsub("\n", "#{'</span>' * @opened.size}#{'</span>' if style}\n#{reopen}#{style}")
316319
end

lib/coderay/helpers/plugin.rb

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ module PluginHost
3030
# * a file could not be found
3131
# * the requested Plugin is not registered
3232
PluginNotFound = Class.new LoadError
33-
HostNotFound = Class.new LoadError
33+
HostNotFound = Class.new LoadError
3434

3535
PLUGIN_HOSTS = []
3636
PLUGIN_HOSTS_BY_ID = {} # dummy hash
@@ -49,8 +49,8 @@ def load_all
4949
def [] id, *args, &blk
5050
plugin = validate_id(id)
5151
begin
52-
plugin = plugin_hash.[] plugin, *args, &blk
53-
end while plugin.is_a? Symbol
52+
plugin = plugin_hash.[](plugin, *args, &blk)
53+
end while plugin.is_a? String
5454
plugin
5555
end
5656

@@ -95,7 +95,7 @@ def plugin_path *args
9595
def map hash
9696
for from, to in hash
9797
from = validate_id from
98-
to = validate_id to
98+
to = validate_id to
9999
plugin_hash[from] = to unless plugin_hash.has_key? from
100100
end
101101
end
@@ -197,22 +197,22 @@ def path_to plugin_id
197197
File.join plugin_path, "#{plugin_id}.rb"
198198
end
199199

200-
# Converts +id+ to a Symbol if it is a String,
201-
# or returns +id+ if it already is a Symbol.
200+
# Converts +id+ to a valid plugin ID String, or returns +nil+.
202201
#
203202
# Raises +ArgumentError+ for all other objects, or if the
204203
# given String includes non-alphanumeric characters (\W).
205204
def validate_id id
206-
if id.is_a? Symbol or id.nil?
207-
id
208-
elsif id.is_a? String
205+
case id
206+
when Symbol
207+
id.to_s
208+
when String
209209
if id[/\w+/] == id
210-
id.downcase.to_sym
210+
id.downcase
211211
else
212212
raise ArgumentError, "Invalid id given: #{id}"
213213
end
214214
else
215-
raise ArgumentError, "String or Symbol expected, but #{id.class} given."
215+
raise ArgumentError, "Symbol or String expected, but #{id.class} given."
216216
end
217217
end
218218

lib/coderay/scanners/debug.rb

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
1+
require 'set'
2+
13
module CodeRay
24
module Scanners
35

46
# = Debug Scanner
57
#
6-
# Interprets the output of the Encoders::Debug encoder.
8+
# Interprets the output of the Encoders::Debug encoder (basically the inverse function).
79
class Debug < Scanner
810

911
register_for :debug
1012
title 'CodeRay Token Dump Import'
1113

1214
protected
1315

16+
def setup
17+
super
18+
@known_token_kinds = TokenKinds.keys.map(&:to_s).to_set
19+
end
20+
1421
def scan_tokens encoder, options
1522

1623
opened_tokens = []
@@ -21,16 +28,19 @@ def scan_tokens encoder, options
2128
encoder.text_token match, :space
2229

2330
elsif match = scan(/ (\w+) \( ( [^\)\\]* ( \\. [^\)\\]* )* ) \)? /x)
24-
kind = self[1].to_sym
25-
match = self[2].gsub(/\\(.)/m, '\1')
26-
unless TokenKinds.has_key? kind
27-
kind = :error
28-
match = matched
31+
if @known_token_kinds.include? self[1]
32+
encoder.text_token self[2].gsub(/\\(.)/m, '\1'), self[1].to_sym
33+
else
34+
encoder.text_token matched, :unknown
2935
end
30-
encoder.text_token match, kind
3136

3237
elsif match = scan(/ (\w+) ([<\[]) /x)
33-
kind = self[1].to_sym
38+
if @known_token_kinds.include? self[1]
39+
kind = self[1].to_sym
40+
else
41+
kind = :unknown
42+
end
43+
3444
opened_tokens << kind
3545
case self[2]
3646
when '<'

lib/coderay/scanners/raydebug.rb

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
require 'set'
2+
13
module CodeRay
24
module Scanners
35

4-
# = Debug Scanner
6+
# = Raydebug Scanner
57
#
6-
# Parses the output of the Encoders::Debug encoder.
8+
# Highlights the output of the Encoders::Debug encoder.
79
class Raydebug < Scanner
810

911
register_for :raydebug
@@ -12,6 +14,11 @@ class Raydebug < Scanner
1214

1315
protected
1416

17+
def setup
18+
super
19+
@known_token_kinds = TokenKinds.keys.map(&:to_s).to_set
20+
end
21+
1522
def scan_tokens encoder, options
1623

1724
opened_tokens = []
@@ -26,20 +33,22 @@ def scan_tokens encoder, options
2633
encoder.text_token kind, :class
2734
encoder.text_token '(', :operator
2835
match = self[2]
29-
encoder.text_token match, kind.to_sym unless match.empty?
36+
unless match.empty?
37+
if @known_token_kinds.include? kind
38+
encoder.text_token match, kind.to_sym
39+
else
40+
encoder.text_token match, :plain
41+
end
42+
end
3043
encoder.text_token match, :operator if match = scan(/\)/)
3144

3245
elsif match = scan(/ (\w+) ([<\[]) /x)
33-
kind = self[1]
34-
case self[2]
35-
when '<'
36-
encoder.text_token kind, :class
37-
when '['
38-
encoder.text_token kind, :class
46+
encoder.text_token self[1], :class
47+
if @known_token_kinds.include? self[1]
48+
kind = self[1].to_sym
3949
else
40-
raise 'CodeRay bug: This case should not be reached.'
50+
kind = :unknown
4151
end
42-
kind = kind.to_sym
4352
opened_tokens << kind
4453
encoder.begin_group kind
4554
encoder.text_token self[2], :operator

lib/coderay/scanners/ruby/string_state.rb

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ class StringState < Struct.new :type, :interpreted, :delim, :heredoc,
1616

1717
STRING_PATTERN = Hash.new do |h, k|
1818
delim, interpreted = *k
19-
# delim = delim.dup # workaround for old Ruby
2019
delim_pattern = Regexp.escape(delim)
2120
if closing_paren = CLOSING_PAREN[delim]
2221
delim_pattern << Regexp.escape(closing_paren)
@@ -29,12 +28,13 @@ class StringState < Struct.new :type, :interpreted, :delim, :heredoc,
2928
# '| [|?*+(){}\[\].^$]'
3029
# end
3130

32-
h[k] =
33-
if interpreted && delim != '#'
34-
/ (?= [#{delim_pattern}] | \# [{$@] ) /mx
35-
else
36-
/ (?= [#{delim_pattern}] ) /mx
37-
end
31+
if interpreted && delim != '#'
32+
/ (?= [#{delim_pattern}] | \# [{$@] ) /mx
33+
else
34+
/ (?= [#{delim_pattern}] ) /mx
35+
end.tap do |pattern|
36+
h[k] = pattern if (delim.respond_to?(:ord) ? delim.ord : delim[0]) < 256
37+
end
3838
end
3939

4040
def initialize kind, interpreted, delim, heredoc = false

lib/coderay/token_kinds.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,5 +80,6 @@ module CodeRay
8080
:plain => false # almost all scanners
8181
)
8282

83-
TokenKinds[:method] = TokenKinds[:function]
83+
TokenKinds[:method] = TokenKinds[:function]
84+
TokenKinds[:unknown] = TokenKinds[:plain]
8485
end

test/unit/debug.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ def test_creation
1818
[:begin_group, :string],
1919
['test', :content],
2020
[:end_group, :string],
21-
[:begin_line, :test],
21+
[:begin_line, :head],
2222
["\n", :space],
2323
["\n \t", :space],
2424
[" \n", :space],
2525
["[]", :method],
26-
[:end_line, :test],
26+
[:end_line, :head],
2727
].flatten
2828
TEST_OUTPUT = <<-'DEBUG'.chomp
29-
integer(10)operator((\\\))string<content(test)>test[
29+
integer(10)operator((\\\))string<content(test)>head[
3030
3131
3232
method([])]
@@ -62,10 +62,10 @@ def test_creation
6262
[:begin_group, :string],
6363
['test', :content],
6464
[:end_group, :string],
65-
[:begin_line, :test],
65+
[:begin_line, :unknown],
6666
["\n\n \t \n", :space],
6767
["[]", :method],
68-
[:end_line, :test],
68+
[:end_line, :unknown],
6969
].flatten
7070

7171
def test_filtering_text_tokens

0 commit comments

Comments
 (0)