diff --git a/.travis.yml b/.travis.yml index 2dbb540890..de76b7a47d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,16 @@ +--- +after_script: +- rake travis:after -t before_script: - - gem install hoe - - rake check_extra_deps -rvm: - - 1.8.7 - - 1.9.2 - - 1.9.3 - - ruby-head +- gem install hoe-travis --no-rdoc --no-ri +- rake travis:before -t +language: ruby notifications: email: - - drbrain@segment7.net + - drbrain@segment7.net +rvm: +- 1.8.7 +- 1.9.2 +- 1.9.3 +- 2.0.0 +script: rake travis diff --git a/CVE-2013-0256.rdoc b/CVE-2013-0256.rdoc new file mode 100644 index 0000000000..b285b6ac45 --- /dev/null +++ b/CVE-2013-0256.rdoc @@ -0,0 +1,49 @@ += RDoc 2.3.0 through 3.12 XSS Exploit + +RDoc documentation generated by rdoc 2.3.0 through rdoc 3.12 and prereleases up +to rdoc 4.0.0.preview2.1 are vulnerable to an XSS exploit. This exploit may +lead to cookie disclosure to third parties. + +The exploit exists in darkfish.js which is copied from the RDoc install +location to the generated documentation. + +RDoc is a static documentation generation tool. Patching the library itself +is insufficient to correct this exploit. Those hosting rdoc documentation will +need to apply the following patch. If applied while ignoring whitespace, this +patch will correct all affected versions: + + diff --git darkfish.js darkfish.js + index 4be722f..f26fd45 100644 + --- darkfish.js + +++ darkfish.js + @@ -109,13 +109,15 @@ function hookSearch() { + function highlightTarget( anchor ) { + console.debug( "Highlighting target '%s'.", anchor ); + + - $("a[name=" + anchor + "]").each( function() { + - if ( !$(this).parent().parent().hasClass('target-section') ) { + - console.debug( "Wrapping the target-section" ); + - $('div.method-detail').unwrap( 'div.target-section' ); + - $(this).parent().wrap( '
' ); + - } else { + - console.debug( "Already wrapped." ); + + $("a[name]").each( function() { + + if ( $(this).attr("name") == anchor ) { + + if ( !$(this).parent().parent().hasClass('target-section') ) { + + console.debug( "Wrapping the target-section" ); + + $('div.method-detail').unwrap( 'div.target-section' ); + + $(this).parent().wrap( '
' ); + + } else { + + console.debug( "Already wrapped." ); + + } + } + }); + }; + +RDoc 3.9.5, 3.12.1 and RDoc 4.0.0.rc.2 and newer are not vulnerable to this +exploit. + +This exploit was discovered by Evgeny Ermakov . + +This vulnerability has been assigned the CVE identifier CVE-2013-0256. + diff --git a/History.rdoc b/History.rdoc index f184dd74d6..f0a5ae9b68 100644 --- a/History.rdoc +++ b/History.rdoc @@ -1,3 +1,25 @@ +=== 3.12.2 / 2013-02-24 + +* Bug fixes + * Fixed bug in syntax-highlighting that would corrupt regular expressions. + Ruby Bug #6488 by Benny Lyne Amorsen. + * Fixed lexing of character syntax (?x). Reported by Xavier + Noria. + * Fixed tokenization of % when it is not followed by a $-string type + * Fixed display of __END__ in documentation examples in HTML output + * Fixed tokenization of reserved words used as new-style hash keys + * Fixed HEREDOC output for the limited case of a heredoc followed by a line + end. When a HEREDOC is not followed by a line end RDoc is not currently + smart enough to restore the source correctly. Bug #162 by Zachary Scott. + +=== 3.12.1 / 2013-02-05 + +* Bug fixes + * Fixed an XSS exploit in darkfish.js. This could lead to cookie disclosure + to third parties. See CVE-2013-0256[rdoc-ref:CVE-2013-0256.rdoc] for full + details including a patch you can apply to generated RDoc documentation. + * Ensured that rd parser files are generated before checking the manifest. + === 3.12 / 2011-12-15 * Minor enhancements diff --git a/Manifest.txt b/Manifest.txt index 4ace335c64..5f2f9528d5 100644 --- a/Manifest.txt +++ b/Manifest.txt @@ -1,5 +1,6 @@ .autotest .document +CVE-2013-0256.rdoc DEVELOPERS.rdoc History.rdoc LEGAL.rdoc @@ -128,8 +129,10 @@ lib/rdoc/parser/ruby_tools.rb lib/rdoc/parser/simple.rb lib/rdoc/parser/text.rb lib/rdoc/rd.rb +lib/rdoc/rd/block_parser.rb lib/rdoc/rd/block_parser.ry lib/rdoc/rd/inline.rb +lib/rdoc/rd/inline_parser.rb lib/rdoc/rd/inline_parser.ry lib/rdoc/rdoc.rb lib/rdoc/require.rb diff --git a/Rakefile b/Rakefile index aad7380b54..e4a19c7996 100644 --- a/Rakefile +++ b/Rakefile @@ -42,6 +42,7 @@ Depending on your version of ruby, you may need to install ruby rdoc/ri data: self.testlib = :minitest self.extra_rdoc_files += %w[ DEVELOPERS.rdoc + CVE-2013-0256.rdoc History.rdoc LICENSE.rdoc LEGAL.rdoc @@ -64,6 +65,7 @@ Depending on your version of ruby, you may need to install ruby rdoc/ri data: end task :generate => PARSER_FILES +task :check_manifest => :generate rule '.rb' => '.ry' do |t| racc = Gem.bin_path 'racc', 'racc' diff --git a/lib/rdoc.rb b/lib/rdoc.rb index 86a4ecf5f2..b03f8270f8 100644 --- a/lib/rdoc.rb +++ b/lib/rdoc.rb @@ -108,7 +108,7 @@ def self.const_missing const_name # :nodoc: ## # RDoc version you are using - VERSION = '3.12' + VERSION = '3.12.2' ## # Method visibilities diff --git a/lib/rdoc/generator/template/darkfish/js/darkfish.js b/lib/rdoc/generator/template/darkfish/js/darkfish.js index 4be722fac3..f26fd45d3a 100644 --- a/lib/rdoc/generator/template/darkfish/js/darkfish.js +++ b/lib/rdoc/generator/template/darkfish/js/darkfish.js @@ -109,13 +109,15 @@ function hookSearch() { function highlightTarget( anchor ) { console.debug( "Highlighting target '%s'.", anchor ); - $("a[name=" + anchor + "]").each( function() { - if ( !$(this).parent().parent().hasClass('target-section') ) { - console.debug( "Wrapping the target-section" ); - $('div.method-detail').unwrap( 'div.target-section' ); - $(this).parent().wrap( '
' ); - } else { - console.debug( "Already wrapped." ); + $("a[name]").each( function() { + if ( $(this).attr("name") == anchor ) { + if ( !$(this).parent().parent().hasClass('target-section') ) { + console.debug( "Wrapping the target-section" ); + $('div.method-detail').unwrap( 'div.target-section' ); + $(this).parent().wrap( '
' ); + } else { + console.debug( "Already wrapped." ); + } } }); }; diff --git a/lib/rdoc/ruby_lex.rb b/lib/rdoc/ruby_lex.rb index 209f01133f..e6e0b41aab 100644 --- a/lib/rdoc/ruby_lex.rb +++ b/lib/rdoc/ruby_lex.rb @@ -412,7 +412,7 @@ def token def lex_init() @OP = IRB::SLex.new @OP.def_rules("\0", "\004", "\032") do |op, io| - Token(TkEND_OF_SCRIPT) + Token(TkEND_OF_SCRIPT, '') end @OP.def_rules(" ", "\t", "\f", "\r", "\13") do |op, io| @@ -536,13 +536,8 @@ def lex_init() @lex_state = EXPR_BEG; Token(TkQUESTION) else - str = ch - if ch == '\\' - str << read_escape - end @lex_state = EXPR_END - str << (ch.respond_to?(:ord) ? ch.ord : ch[0]) - Token(TkINTEGER, str) + Token(TkSTRING, ch) end end end @@ -813,7 +808,8 @@ def lex_int2 @OP.def_rule("_") do if peek_match?(/_END__/) and @lex_state == EXPR_BEG then - Token(TkEND_OF_SCRIPT) + 6.times { getc } + Token(TkEND_OF_SCRIPT, '__END__') else ungetc identify_identifier @@ -861,7 +857,7 @@ def identify_gvar end IDENT_RE = if defined? Encoding then - /[\w\u0080-\uFFFF]/u + eval '/[\w\u{0080}-\u{FFFFF}]/u' # 1.8 can't parse \u{} else /[\w\x80-\xFF]/ end @@ -940,6 +936,8 @@ def identify_identifier @indent += 1 @indent_stack.push token_c end + else + token_c = TkIDENTIFIER end elsif DEINDENT_CLAUSE.include?(token) @@ -984,12 +982,13 @@ def identify_here_document indent = true end if /['"`]/ =~ ch - lt = ch + user_quote = lt = ch quoted = "" while (c = getc) && c != lt quoted.concat c end else + user_quote = nil lt = '"' quoted = ch.dup while (c = getc) && c =~ /\w/ @@ -1009,8 +1008,17 @@ def identify_here_document end end + output_heredoc = reserve.join =~ /\A\r?\n\z/ + + if output_heredoc then + doc = '<<' + doc << '-' if indent + doc << "#{user_quote}#{quoted}#{user_quote}\n" + else + doc = '"' + end + @here_header = false - doc = '"' while l = gets l = l.sub(/(:?\r)?\n\z/, "\n") if (indent ? l.strip : l.chomp) == quoted @@ -1018,7 +1026,12 @@ def identify_here_document end doc << l end - doc << '"' + + if output_heredoc then + doc << l.chomp + else + doc << '"' + end @here_header = true @here_readed.concat reserve @@ -1026,9 +1039,10 @@ def identify_here_document ungetc ch end + token_class = output_heredoc ? RDoc::RubyLex::TkHEREDOC : Ltype2Token[lt] @ltype = ltback @lex_state = EXPR_END - Token(Ltype2Token[lt], doc) + Token(token_class, doc) end def identify_quotation @@ -1039,7 +1053,7 @@ def identify_quotation type = nil lt = "\"" else - raise Error, "unknown type of %string #{type.inspect}" + return Token(TkMOD, '%') end # if ch !~ /\W/ # ungetc @@ -1163,7 +1177,7 @@ def identify_string(ltype, quoted = ltype, type = nil) @ltype = ltype @quoted = quoted - str = if ltype == quoted and %w[" '].include? ltype then + str = if ltype == quoted and %w[" ' /].include? ltype then ltype.dup elsif RUBY_VERSION > '1.9' then "%#{type or PERCENT_LTYPE.key ltype}#{PERCENT_PAREN_REV[quoted]}" @@ -1189,14 +1203,18 @@ def identify_string(ltype, quoted = ltype, type = nil) else ungetc end - elsif ch == '\\' and @ltype == "'" #' - case ch = getc - when "\\", "\n", "'" + elsif ch == '\\' + if %w[' /].include? @ltype then + case ch = getc + when "\\", "\n", "'" + when @ltype + str << ch + else + ungetc + end else - ungetc + str << read_escape end - elsif ch == '\\' #' - str << read_escape end if close then diff --git a/lib/rdoc/ruby_token.rb b/lib/rdoc/ruby_token.rb index 687ef2fe80..d3333bc6a1 100644 --- a/lib/rdoc/ruby_token.rb +++ b/lib/rdoc/ruby_token.rb @@ -331,6 +331,7 @@ def Token(token, value = nil) [:TkINTEGER, TkVal], [:TkFLOAT, TkVal], [:TkSTRING, TkVal], + [:TkHEREDOC, TkVal], [:TkXSTRING, TkVal], [:TkREGEXP, TkVal], [:TkSYMBOL, TkVal], diff --git a/test/test_rdoc_markup_to_html.rb b/test/test_rdoc_markup_to_html.rb index 88acf57ea8..a84270fbb5 100644 --- a/test/test_rdoc_markup_to_html.rb +++ b/test/test_rdoc_markup_to_html.rb @@ -350,14 +350,14 @@ def test_accept_verbatim_parseable_error rdoc.options = options RDoc::RDoc.current = rdoc - verb = @RM::Verbatim.new("a %z'foo' # => blah\n") + verb = @RM::Verbatim.new("a % 09 # => blah\n") @to.start_accepting @to.accept_verbatim verb expected = <<-EXPECTED -
a %z'foo' # => blah
+
a % 09 # => blah
 
EXPECTED diff --git a/test/test_rdoc_markup_to_html_snippet.rb b/test/test_rdoc_markup_to_html_snippet.rb index 067ac9647b..99d9cb778f 100644 --- a/test/test_rdoc_markup_to_html_snippet.rb +++ b/test/test_rdoc_markup_to_html_snippet.rb @@ -379,19 +379,19 @@ def test_accept_verbatim_ruby_error rdoc.options = options RDoc::RDoc.current = rdoc - verb = @RM::Verbatim.new("a %z'foo' # => blah\n") + verb = @RM::Verbatim.new("a % 09 # => blah\n") @to.start_accepting @to.accept_verbatim verb expected = <<-EXPECTED -
a %z'foo' # => blah
+
a % 09 # => blah
 
EXPECTED assert_equal expected, @to.res.join - assert_equal 19, @to.characters + assert_equal 16, @to.characters end def test_add_paragraph @@ -543,11 +543,12 @@ def test_convert_limit_verbatim_multiline This routine modifies its +comment+ parameter. RDOC + inner = CGI.escapeHTML "# Don't display comment from this point forward" expected = <<-EXPECTED

Look for directives in a normal comment block:

# :stopdoc:
-# Don't display comment from this point forward
+#{inner}
EXPECTED actual = @to.convert rdoc diff --git a/test/test_rdoc_ruby_lex.rb b/test/test_rdoc_ruby_lex.rb index 6410c587b7..0dcb42565d 100644 --- a/test/test_rdoc_ruby_lex.rb +++ b/test/test_rdoc_ruby_lex.rb @@ -1,3 +1,5 @@ +# coding: UTF-8 + require 'rdoc/test_case' class TestRDocRubyLex < RDoc::TestCase @@ -30,6 +32,28 @@ def test_class_tokenize assert_equal expected, tokens end + def test_class_tokenize___END__ + tokens = RDoc::RubyLex.tokenize '__END__', nil + + expected = [ + @TK::TkEND_OF_SCRIPT.new(0, 1, 0, '__END__'), + @TK::TkNL .new(7, 1, 7, "\n"), + ] + + assert_equal expected, tokens + end + + def test_class_tokenize_character_literal + tokens = RDoc::RubyLex.tokenize "?\\", nil + + expected = [ + @TK::TkSTRING.new( 0, 1, 0, "\\"), + @TK::TkNL .new( 2, 1, 2, "\n"), + ] + + assert_equal expected, tokens + end + def test_class_tokenize_def_heredoc tokens = RDoc::RubyLex.tokenize <<-'RUBY', nil def x @@ -46,7 +70,8 @@ def x @TK::TkIDENTIFIER.new( 4, 1, 4, 'x'), @TK::TkNL .new( 5, 1, 5, "\n"), @TK::TkSPACE .new( 6, 2, 0, ' '), - @TK::TkSTRING .new( 8, 2, 2, %Q{"Line 1\nLine 2\n"}), + @TK::TkHEREDOC .new( 8, 2, 2, + %Q{<