From 51dcf3405e1e2e1a37b700ae81b8a6dcc67e3df2 Mon Sep 17 00:00:00 2001 From: zhanghua1831 Date: Wed, 17 Mar 2021 10:08:42 +0800 Subject: [PATCH] fix CVE-2020-26247 (cherry picked from commit 5b4d5cdee5e54f3ecad724bf64cfbb3502d955e9) --- CVE-2020-26247-pre.patch | 65 +++++++++ CVE-2020-26247.patch | 278 +++++++++++++++++++++++++++++++++++++++ rubygem-nokogiri.spec | 9 +- 3 files changed, 351 insertions(+), 1 deletion(-) create mode 100644 CVE-2020-26247-pre.patch create mode 100644 CVE-2020-26247.patch diff --git a/CVE-2020-26247-pre.patch b/CVE-2020-26247-pre.patch new file mode 100644 index 0000000..4605d1d --- /dev/null +++ b/CVE-2020-26247-pre.patch @@ -0,0 +1,65 @@ +From 74abb4f2e73bb61b17d9f1a0ad717c881943b877 Mon Sep 17 00:00:00 2001 +From: Aaron Patterson +Date: Wed, 26 Feb 2020 13:51:43 -0800 +Subject: [PATCH] Work around a bug in libxml2 + +This commit works around a bug in libxml2 where parsing schemas can +result in dangling pointers which can lead to a segv. + +Upstream bug is here: https://gitlab.gnome.org/GNOME/libxml2/issues/148 + +Fixes #1985 +--- + ext/nokogiri/xml_schema.c | 29 +++++++++++++++++++++++++++++ + 1 file changed, 29 insertions(+) + +diff --git a/ext/nokogiri/xml_schema.c b/ext/nokogiri/xml_schema.c +index da2774b..439f721 100644 +--- a/ext/nokogiri/xml_schema.c ++++ b/ext/nokogiri/xml_schema.c +@@ -133,6 +133,31 @@ static VALUE read_memory(VALUE klass, VALUE content) + return rb_schema; + } + ++/* Schema creation will remove and deallocate "blank" nodes. ++ * If those blank nodes have been exposed to Ruby, they could get freed ++ * out from under the VALUE pointer. This function checks to see if any of ++ * those nodes have been exposed to Ruby, and if so we should raise an exception. ++ */ ++static int has_blank_nodes_p(VALUE cache) ++{ ++ long i; ++ ++ if (NIL_P(cache)) { ++ return 0; ++ } ++ ++ for (i = 0; i < RARRAY_LEN(cache); i++) { ++ xmlNodePtr node; ++ VALUE element = rb_ary_entry(cache, i); ++ Data_Get_Struct(element, xmlNode, node); ++ if (xmlIsBlankNode(node)) { ++ return 1; ++ } ++ } ++ ++ return 0; ++} ++ + /* + * call-seq: + * from_document(doc) +@@ -152,6 +177,10 @@ static VALUE from_document(VALUE klass, VALUE document) + /* In case someone passes us a node. ugh. */ + doc = doc->doc; + ++ if (has_blank_nodes_p(DOC_NODE_CACHE(doc))) { ++ rb_raise(rb_eArgError, "Creating a schema from a document that has blank nodes exposed to Ruby is dangerous"); ++ } ++ + ctx = xmlSchemaNewDocParserCtxt(doc); + + errors = rb_ary_new(); +-- +2.23.0 + diff --git a/CVE-2020-26247.patch b/CVE-2020-26247.patch new file mode 100644 index 0000000..e2a9a1c --- /dev/null +++ b/CVE-2020-26247.patch @@ -0,0 +1,278 @@ +From 9c87439d9afa14a365ff13e73adc809cb2c3d97b Mon Sep 17 00:00:00 2001 +From: Mike Dalessio +Date: Mon, 23 Nov 2020 00:47:02 -0500 +Subject: [PATCH] feat: XML::Schema and RelaxNG creation accept optional + ParseOptions + +I'm trying out a new pattern, which is that the parsed object carries +around the ParseOptions it was created with, which should make some +testing a bit easier. + +I'm also not implementing the "config block" pattern in use for +Documents, because I think the UX is weird and I'm hoping to change +everything to use kwargs in a 2.0 release, anyway. +--- + ext/nokogiri/xml_relax_ng.c | 39 ++++++++++++++++++-------- + ext/nokogiri/xml_schema.c | 46 +++++++++++++++++++++++-------- + lib/nokogiri/xml/parse_options.rb | 2 ++ + lib/nokogiri/xml/relax_ng.rb | 4 +-- + lib/nokogiri/xml/schema.rb | 10 ++++--- + 5 files changed, 72 insertions(+), 29 deletions(-) + +diff --git a/ext/nokogiri/xml_relax_ng.c b/ext/nokogiri/xml_relax_ng.c +index e17b11a..f361d27 100644 +--- a/ext/nokogiri/xml_relax_ng.c ++++ b/ext/nokogiri/xml_relax_ng.c +@@ -53,16 +53,24 @@ static VALUE validate_document(VALUE self, VALUE document) + * + * Create a new RelaxNG from the contents of +string+ + */ +-static VALUE read_memory(VALUE klass, VALUE content) ++static VALUE read_memory(int argc, VALUE *argv, VALUE klass) + { +- xmlRelaxNGParserCtxtPtr ctx = xmlRelaxNGNewMemParserCtxt( +- (const char *)StringValuePtr(content), +- (int)RSTRING_LEN(content) +- ); ++ VALUE content; ++ VALUE parse_options; ++ xmlRelaxNGParserCtxtPtr ctx; + xmlRelaxNGPtr schema; +- VALUE errors = rb_ary_new(); ++ VALUE errors; + VALUE rb_schema; ++ int scanned_args = 0; ++ ++ scanned_args = rb_scan_args(argc, argv, "11", &content, &parse_options); ++ if (scanned_args == 1) { ++ parse_options = rb_const_get(rb_const_get(mNokogiriXml, rb_intern("ParseOptions")), rb_intern("DEFAULT_SCHEMA")); ++ } + ++ ctx = xmlRelaxNGNewMemParserCtxt((const char *)StringValuePtr(content), (int)RSTRING_LEN(content)); ++ ++ errors = rb_ary_new(); + xmlSetStructuredErrorFunc((void *)errors, Nokogiri_error_array_pusher); + + #ifdef HAVE_XMLRELAXNGSETPARSERSTRUCTUREDERRORS +@@ -90,6 +98,7 @@ static VALUE read_memory(VALUE klass, VALUE content) + + rb_schema = Data_Wrap_Struct(klass, 0, dealloc, schema); + rb_iv_set(rb_schema, "@errors", errors); ++ rb_iv_set(rb_schema, "@parse_options", parse_options); + + return rb_schema; + } +@@ -100,18 +109,25 @@ static VALUE read_memory(VALUE klass, VALUE content) + * + * Create a new RelaxNG schema from the Nokogiri::XML::Document +doc+ + */ +-static VALUE from_document(VALUE klass, VALUE document) ++static VALUE from_document(int argc, VALUE *argv, VALUE klass) + { ++ VALUE document; ++ VALUE parse_options; + xmlDocPtr doc; + xmlRelaxNGParserCtxtPtr ctx; + xmlRelaxNGPtr schema; + VALUE errors; + VALUE rb_schema; ++ int scanned_args = 0; ++ ++ scanned_args = rb_scan_args(argc, argv, "11", &document, &parse_options); + + Data_Get_Struct(document, xmlDoc, doc); ++ doc = doc->doc; /* In case someone passes us a node. ugh. */ + +- /* In case someone passes us a node. ugh. */ +- doc = doc->doc; ++ if (scanned_args == 1) { ++ parse_options = rb_const_get(rb_const_get(mNokogiriXml, rb_intern("ParseOptions")), rb_intern("DEFAULT_SCHEMA")); ++ } + + ctx = xmlRelaxNGNewDocParserCtxt(doc); + +@@ -142,6 +158,7 @@ static VALUE from_document(VALUE klass, VALUE document) + + rb_schema = Data_Wrap_Struct(klass, 0, dealloc, schema); + rb_iv_set(rb_schema, "@errors", errors); ++ rb_iv_set(rb_schema, "@parse_options", parse_options); + + return rb_schema; + } +@@ -155,7 +172,7 @@ void init_xml_relax_ng() + + cNokogiriXmlRelaxNG = klass; + +- rb_define_singleton_method(klass, "read_memory", read_memory, 1); +- rb_define_singleton_method(klass, "from_document", from_document, 1); ++ rb_define_singleton_method(klass, "read_memory", read_memory, -1); ++ rb_define_singleton_method(klass, "from_document", from_document, -1); + rb_define_private_method(klass, "validate_document", validate_document, 1); + } +diff --git a/ext/nokogiri/xml_schema.c b/ext/nokogiri/xml_schema.c +index 439f721..ea7c3d3 100644 +--- a/ext/nokogiri/xml_schema.c ++++ b/ext/nokogiri/xml_schema.c +@@ -93,15 +93,26 @@ static VALUE validate_file(VALUE self, VALUE rb_filename) + * + * Create a new Schema from the contents of +string+ + */ +-static VALUE read_memory(VALUE klass, VALUE content) ++static VALUE read_memory(int argc, VALUE *argv, VALUE klass) + { ++ VALUE content; ++ VALUE parse_options; ++ int parse_options_int; ++ xmlSchemaParserCtxtPtr ctx; + xmlSchemaPtr schema; +- xmlSchemaParserCtxtPtr ctx = xmlSchemaNewMemParserCtxt( +- (const char *)StringValuePtr(content), +- (int)RSTRING_LEN(content) +- ); ++ VALUE errors; + VALUE rb_schema; +- VALUE errors = rb_ary_new(); ++ int scanned_args = 0; ++ ++ scanned_args = rb_scan_args(argc, argv, "11", &content, &parse_options); ++ if (scanned_args == 1) { ++ parse_options = rb_const_get(rb_const_get(mNokogiriXml, rb_intern("ParseOptions")), rb_intern("DEFAULT_SCHEMA")); ++ } ++ parse_options_int = (int)NUM2INT(rb_funcall(parse_options, rb_intern("to_i"), 0)); ++ ++ ctx = xmlSchemaNewMemParserCtxt((const char *)StringValuePtr(content), (int)RSTRING_LEN(content)); ++ ++ errors = rb_ary_new(); + xmlSetStructuredErrorFunc((void *)errors, Nokogiri_error_array_pusher); + + #ifdef HAVE_XMLSCHEMASETPARSERSTRUCTUREDERRORS +@@ -109,7 +120,7 @@ static VALUE read_memory(VALUE klass, VALUE content) + ctx, + Nokogiri_error_array_pusher, + (void *)errors +- ); ++ ); + #endif + + schema = xmlSchemaParse(ctx); +@@ -129,6 +140,7 @@ static VALUE read_memory(VALUE klass, VALUE content) + + rb_schema = Data_Wrap_Struct(klass, 0, dealloc, schema); + rb_iv_set(rb_schema, "@errors", errors); ++ rb_iv_set(rb_schema, "@parse_options", parse_options); + + return rb_schema; + } +@@ -164,18 +176,27 @@ static int has_blank_nodes_p(VALUE cache) + * + * Create a new Schema from the Nokogiri::XML::Document +doc+ + */ +-static VALUE from_document(VALUE klass, VALUE document) ++static VALUE from_document(int argc, VALUE *argv, VALUE klass) + { ++ VALUE document; ++ VALUE parse_options; ++ int parse_options_int; + xmlDocPtr doc; + xmlSchemaParserCtxtPtr ctx; + xmlSchemaPtr schema; + VALUE errors; + VALUE rb_schema; ++ int scanned_args = 0; ++ ++ scanned_args = rb_scan_args(argc, argv, "11", &document, &parse_options); + + Data_Get_Struct(document, xmlDoc, doc); ++ doc = doc->doc; /* In case someone passes us a node. ugh. */ + +- /* In case someone passes us a node. ugh. */ +- doc = doc->doc; ++ if (scanned_args == 1) { ++ parse_options = rb_const_get(rb_const_get(mNokogiriXml, rb_intern("ParseOptions")), rb_intern("DEFAULT_SCHEMA")); ++ } ++ parse_options_int = (int)NUM2INT(rb_funcall(parse_options, rb_intern("to_i"), 0)); + + if (has_blank_nodes_p(DOC_NODE_CACHE(doc))) { + rb_raise(rb_eArgError, "Creating a schema from a document that has blank nodes exposed to Ruby is dangerous"); +@@ -211,6 +232,7 @@ static VALUE from_document(VALUE klass, VALUE document) + + rb_schema = Data_Wrap_Struct(klass, 0, dealloc, schema); + rb_iv_set(rb_schema, "@errors", errors); ++ rb_iv_set(rb_schema, "@parse_options", parse_options); + + return rb_schema; + +@@ -226,8 +248,8 @@ void init_xml_schema() + + cNokogiriXmlSchema = klass; + +- rb_define_singleton_method(klass, "read_memory", read_memory, 1); +- rb_define_singleton_method(klass, "from_document", from_document, 1); ++ rb_define_singleton_method(klass, "read_memory", read_memory, -1); ++ rb_define_singleton_method(klass, "from_document", from_document, -1); + + rb_define_private_method(klass, "validate_document", validate_document, 1); + rb_define_private_method(klass, "validate_file", validate_file, 1); +diff --git a/lib/nokogiri/xml/parse_options.rb b/lib/nokogiri/xml/parse_options.rb +index 8969578..c6d3d1c 100644 +--- a/lib/nokogiri/xml/parse_options.rb ++++ b/lib/nokogiri/xml/parse_options.rb +@@ -72,6 +72,8 @@ module Nokogiri + DEFAULT_XML = RECOVER | NONET + # the default options used for parsing HTML documents + DEFAULT_HTML = RECOVER | NOERROR | NOWARNING | NONET ++ # the default options used for parsing XML schemas ++ DEFAULT_SCHEMA = NONET + + attr_accessor :options + def initialize options = STRICT +diff --git a/lib/nokogiri/xml/relax_ng.rb b/lib/nokogiri/xml/relax_ng.rb +index 5a645a4..79bc30c 100644 +--- a/lib/nokogiri/xml/relax_ng.rb ++++ b/lib/nokogiri/xml/relax_ng.rb +@@ -4,8 +4,8 @@ module Nokogiri + ### + # Create a new Nokogiri::XML::RelaxNG document from +string_or_io+. + # See Nokogiri::XML::RelaxNG for an example. +- def RelaxNG string_or_io +- RelaxNG.new(string_or_io) ++ def RelaxNG(string_or_io, options = ParseOptions::DEFAULT_SCHEMA) ++ RelaxNG.new(string_or_io, options) + end + end + +diff --git a/lib/nokogiri/xml/schema.rb b/lib/nokogiri/xml/schema.rb +index 65a7bcd..a88f69c 100644 +--- a/lib/nokogiri/xml/schema.rb ++++ b/lib/nokogiri/xml/schema.rb +@@ -4,8 +4,8 @@ module Nokogiri + ### + # Create a new Nokogiri::XML::Schema object using a +string_or_io+ + # object. +- def Schema string_or_io +- Schema.new(string_or_io) ++ def Schema(string_or_io, options = ParseOptions::DEFAULT_SCHEMA) ++ Schema.new(string_or_io, options) + end + end + +@@ -29,12 +29,14 @@ module Nokogiri + class Schema + # Errors while parsing the schema file + attr_accessor :errors ++ # The Nokogiri::XML::ParseOptions used to parse the schema ++ attr_accessor :parse_options + + ### + # Create a new Nokogiri::XML::Schema object using a +string_or_io+ + # object. +- def self.new string_or_io +- from_document Nokogiri::XML(string_or_io) ++ def self.new string_or_io, options = ParseOptions::DEFAULT_SCHEMA ++ from_document(Nokogiri::XML(string_or_io), options) + end + + ### +-- +2.23.0 + diff --git a/rubygem-nokogiri.spec b/rubygem-nokogiri.spec index ae717ae..2515526 100644 --- a/rubygem-nokogiri.spec +++ b/rubygem-nokogiri.spec @@ -7,13 +7,15 @@ Summary: An HTML, XML, SAX, and Reader parser Name: rubygem-%{gem_name} Version: %{mainver} -Release: 1 +Release: 2 License: MIT URL: https://nokogiri.org Source0: https://rubygems.org/gems/%{gem_name}-%{mainver}%{?prever}.gem Source1: https://github.com/sparklemotion/%{gem_name}/archive/v%{mainver}.tar.gz # Shut down libxml2 version unmatching warning Patch0: %{name}-1.6.6.4-shutdown-libxml2-warning.patch +Patch1: CVE-2020-26247-pre.patch +Patch2: CVE-2020-26247.patch BuildRequires: ruby(release) ruby(rubygems) rubygem(minitest) rubygems-devel Obsoletes: ruby-%{gem_name} <= 1.5.2-2 BuildRequires: gcc rubygem(pkg-config) libxml2-devel libxslt-devel ruby-devel @@ -53,6 +55,8 @@ pushd tmpunpackdir gem unpack %{SOURCE0} cd %{gem_name}-%{version} %patch0 -p1 +%patch1 -p1 +%patch2 -p1 gem specification -l --ruby %{SOURCE0} > %{gem_name}.gemspec sed -i \ -e 's|, "ports/archives/[^"][^"]*"||g' \ @@ -145,5 +149,8 @@ popd %{gem_dir}/doc/%{gem_name}-%{mainver}%{?prever}/ %changelog +* Wed Mar 17 2021 zhanghua - 1.10.5-2 +- fix CVE-2020-26247 + * Wed Aug 19 2020 luoshengwei - 1.10.5-1 - package init -- Gitee