Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for configuration of XSLT security #1789

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions ext/java/nokogiri/XsltStylesheet.java
Original file line number Diff line number Diff line change
Expand Up @@ -350,4 +350,22 @@ public class XsltStylesheet extends RubyObject
if (arg instanceof XmlDocument) { return; }
throw runtime.newArgumentError("argument must be a Nokogiri::XML::Document");
}

@JRubyMethod(meta = true, rest = true, name = "default_security_options")
public static IRubyObject
get_default_security_options(ThreadContext context, IRubyObject klazz, IRubyObject[] args)
{
// This method is not supported because the Java XML backend does not support the
// security controls supported by the libxml backend
throw context.getRuntime().newNotImplementedError("Nokogiri::XSLT::Stylesheet.default_security_options method is not implemented");
}

@JRubyMethod(meta = true, rest = true, name = "default_security_options=")
public static IRubyObject
set_default_security_options(ThreadContext context, IRubyObject klazz, IRubyObject[] args)
{
// This method is not supported because the Java XML backend does not support the
// security controls supported by the libxml backend
throw context.getRuntime().newNotImplementedError("Nokogiri::XSLT::Stylesheet.default_security_prefs= method is not implemented");
}
}
1 change: 1 addition & 0 deletions ext/nokogiri/nokogiri.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
#include <libxslt/extensions.h>
#include <libxslt/xsltconfig.h>
#include <libxslt/xsltutils.h>
#include <libxslt/security.h>
#include <libxslt/transform.h>
#include <libxslt/imports.h>
#include <libxslt/xsltInternals.h>
Expand Down
50 changes: 50 additions & 0 deletions ext/nokogiri/xslt_stylesheet.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#include <nokogiri.h>

VALUE cNokogiriXsltStylesheet ;
VALUE mNokogiriXsltSecurity ;
VALUE cNokogiriXsltSecurityConfig ;

static void
mark(void *data)
Expand Down Expand Up @@ -400,17 +402,65 @@ rb_xslt_s_register(VALUE self, VALUE uri, VALUE obj)
return self;
}

static void
add_sec_option(xsltSecurityPrefsPtr xsltPrefs, int option, VALUE val)
{
if (val == Qtrue) {
xsltSetSecurityPrefs(xsltPrefs, option, xsltSecurityAllow);
} else if (val == Qfalse) {
xsltSetSecurityPrefs(xsltPrefs, option, xsltSecurityForbid);
}
}

static VALUE
rb_set_default_security_options(VALUE self, VALUE options)
{
Check_Type(options, T_OBJECT);
xsltSecurityPrefsPtr oldDefaults = xsltGetDefaultSecurityPrefs();
xsltSecurityPrefsPtr xsltPrefs = xsltNewSecurityPrefs();
add_sec_option(xsltPrefs, XSLT_SECPREF_READ_FILE, rb_iv_get(options, "@allow_read_file"));
add_sec_option(xsltPrefs, XSLT_SECPREF_WRITE_FILE, rb_iv_get(options, "@allow_write_file"));
add_sec_option(xsltPrefs, XSLT_SECPREF_CREATE_DIRECTORY, rb_iv_get(options, "@allow_create_directory"));
add_sec_option(xsltPrefs, XSLT_SECPREF_READ_NETWORK, rb_iv_get(options, "@allow_read_network"));
add_sec_option(xsltPrefs, XSLT_SECPREF_WRITE_NETWORK, rb_iv_get(options, "@allow_write_network"));
xsltSetDefaultSecurityPrefs(xsltPrefs);
if(oldDefaults) {
xsltFreeSecurityPrefs(oldDefaults);
}
return Qnil;
}

static VALUE
rb_get_default_security_options(VALUE self)
{
VALUE prefs = rb_funcall(cNokogiriXsltSecurityConfig, rb_intern("new"), 0);
xsltSecurityPrefsPtr xsltPrefs = xsltGetDefaultSecurityPrefs();
if(xsltPrefs == NULL) {
return prefs;
}
rb_iv_set(prefs, "@allow_read_file", xsltGetSecurityPrefs(xsltPrefs, XSLT_SECPREF_READ_FILE) == xsltSecurityAllow ? Qtrue : Qfalse);
rb_iv_set(prefs, "@allow_write_file", xsltGetSecurityPrefs(xsltPrefs, XSLT_SECPREF_WRITE_FILE) == xsltSecurityAllow ? Qtrue : Qfalse);
rb_iv_set(prefs, "@allow_create_directory", xsltGetSecurityPrefs(xsltPrefs, XSLT_SECPREF_CREATE_DIRECTORY) == xsltSecurityAllow ? Qtrue : Qfalse);
rb_iv_set(prefs, "@allow_read_network", xsltGetSecurityPrefs(xsltPrefs, XSLT_SECPREF_READ_NETWORK) == xsltSecurityAllow ? Qtrue : Qfalse);
rb_iv_set(prefs, "@allow_write_network", xsltGetSecurityPrefs(xsltPrefs, XSLT_SECPREF_WRITE_NETWORK) == xsltSecurityAllow ? Qtrue : Qfalse);
return prefs;
}

void
noko_init_xslt_stylesheet(void)
{
rb_define_singleton_method(mNokogiriXslt, "register", rb_xslt_s_register, 2);
rb_iv_set(mNokogiriXslt, "@modules", rb_hash_new());

cNokogiriXsltStylesheet = rb_define_class_under(mNokogiriXslt, "Stylesheet", rb_cObject);
mNokogiriXsltSecurity = rb_define_module_under(mNokogiriXslt, "Security");
cNokogiriXsltSecurityConfig = rb_define_class_under(mNokogiriXsltSecurity, "Config", rb_cObject);

rb_undef_alloc_func(cNokogiriXsltStylesheet);

rb_define_singleton_method(cNokogiriXsltStylesheet, "parse_stylesheet_doc", parse_stylesheet_doc, 1);
rb_define_singleton_method(cNokogiriXsltStylesheet, "default_security_options", rb_get_default_security_options, 0);
rb_define_singleton_method(cNokogiriXsltStylesheet, "default_security_options=", rb_set_default_security_options, 1);
rb_define_method(cNokogiriXsltStylesheet, "serialize", rb_xslt_stylesheet_serialize, 1);
rb_define_method(cNokogiriXsltStylesheet, "transform", rb_xslt_stylesheet_transform, -1);
}
18 changes: 18 additions & 0 deletions lib/nokogiri/xslt.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# coding: utf-8
# frozen_string_literal: true

require_relative "xslt/security"

module Nokogiri
class << self
###
Expand All @@ -19,6 +21,8 @@ def XSLT(stylesheet, modules = {})
# See Nokogiri::XSLT::Stylesheet for creating and manipulating
# Stylesheet object.
module XSLT
include Nokogiri::XSLT::Security

class << self
# :call-seq:
# parse(xsl) → Nokogiri::XSLT::Stylesheet
Expand Down Expand Up @@ -86,6 +90,20 @@ def parse(string, modules = {})
end
end

###
# Get the default security options used by libxslt
# [Returns] an object of type Nokogiri::XSLT::Security::Config
def default_security_options
Stylesheet.default_security_options
end

###
# Set the default security options used by libxslt
# +options+ should be an object of type Nokogiri::XSLT::Security::Config
def default_security_options=(options)
Stylesheet.default_security_options = (options)
end

# :call-seq:
# quote_params(params) → Array
#
Expand Down
24 changes: 24 additions & 0 deletions lib/nokogiri/xslt/security.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# frozen_string_literal: true

module Nokogiri
module XSLT
module Security
class Config
attr_accessor :allow_read_file
attr_accessor :allow_write_file
attr_accessor :allow_create_directory
attr_accessor :allow_read_network
attr_accessor :allow_write_network

# Mirror xslt (implicit) internal defaults
def initialize
@allow_read_file = true
@allow_write_file = true
@allow_create_directory = true
@allow_read_network = true
@allow_write_network = true
end
end
end
end
end
1 change: 1 addition & 0 deletions nokogiri.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ Gem::Specification.new do |spec|
"lib/nokogiri/xml/xpath/syntax_error.rb",
"lib/nokogiri/xml/xpath_context.rb",
"lib/nokogiri/xslt.rb",
"lib/nokogiri/xslt/security.rb",
"lib/nokogiri/xslt/stylesheet.rb",
"lib/xsd/xmlparser/nokogiri.rb",
]
Expand Down
6 changes: 6 additions & 0 deletions test/files/xslt_included.xsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version='1.0' encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version='1.0'>
<xsl:output method="text" indent="no" encoding="UTF-8"/>

</xsl:stylesheet>
7 changes: 7 additions & 0 deletions test/files/xslt_including.xsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version='1.0' encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version='1.0'>
<xsl:output method="text" indent="no" encoding="UTF-8"/>

<xsl:include href="./xslt_included.xsl"/>
</xsl:stylesheet>
1 change: 1 addition & 0 deletions test/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ module TestBase
XML_XINCLUDE_FILE = File.join(ASSETS_DIR, "xinclude.xml")
XML_ATOM_FILE = File.join(ASSETS_DIR, "atom.xml")
XSLT_FILE = File.join(ASSETS_DIR, "staff.xslt")
XSLT_INCLUDING_FILE = File.join(ASSETS_DIR, "xslt_including.xsl")
XPATH_FILE = File.join(ASSETS_DIR, "slow-xpath.xml")

def i_am_ruby_matching(gem_version_requirement_string)
Expand Down
93 changes: 93 additions & 0 deletions test/test_xslt_transforms.rb
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,99 @@ def test_transform_with_quote_params
assert_equal("Booyah", result_doc.at_css("h1").content)
end

def test_default_security_options_read_file
options = Nokogiri::XSLT::Security::Config.new

if Nokogiri.jruby?
assert_raises(NotImplementedError) do
Nokogiri::XSLT.default_security_options = options
end
else
# Default is insecure
Nokogiri::XSLT.default_security_options = options
assert(Nokogiri::XSLT(File.open(XSLT_INCLUDING_FILE)))

# libxslt/libxml2 makes some funny choices on Windows that lead to local paths
# being considered network paths (maybe due to the leading C:/)
options.allow_read_file = false
options.allow_read_network = false
Nokogiri::XSLT.default_security_options = options
assert_raises(RuntimeError) { Nokogiri::XSLT(File.open(XSLT_INCLUDING_FILE)) }

options.allow_read_file = true
options.allow_read_network = true
Nokogiri::XSLT.default_security_options = options
assert(Nokogiri::XSLT(File.open(XSLT_INCLUDING_FILE)))
end
end

def test_default_security_options_roundtrip
options = Nokogiri::XSLT::Security::Config.new

if Nokogiri.jruby?
assert_raises(NotImplementedError) do
Nokogiri::XSLT.default_security_options
end
else
# Since the getter constructs the settings from the underlying C library
# this actually tells us if the settings are configured correctly
# Check all settings once per option to make sure fields are plumbed right
# at every stage
Nokogiri::XSLT.default_security_options = options
configured_opts = Nokogiri::XSLT.default_security_options
assert(configured_opts.allow_read_file)
assert(configured_opts.allow_write_file)
assert(configured_opts.allow_create_directory)
assert(configured_opts.allow_read_network)
assert(configured_opts.allow_write_network)

options.allow_read_file = false
Nokogiri::XSLT.default_security_options = options
configured_opts = Nokogiri::XSLT.default_security_options
refute(configured_opts.allow_read_file)
assert(configured_opts.allow_write_file)
assert(configured_opts.allow_create_directory)
assert(configured_opts.allow_read_network)
assert(configured_opts.allow_write_network)

options.allow_write_file = false
Nokogiri::XSLT.default_security_options = options
configured_opts = Nokogiri::XSLT.default_security_options
refute(configured_opts.allow_read_file)
refute(configured_opts.allow_write_file)
assert(configured_opts.allow_create_directory)
assert(configured_opts.allow_read_network)
assert(configured_opts.allow_write_network)

options.allow_create_directory = false
Nokogiri::XSLT.default_security_options = options
configured_opts = Nokogiri::XSLT.default_security_options
refute(configured_opts.allow_read_file)
refute(configured_opts.allow_write_file)
refute(configured_opts.allow_create_directory)
assert(configured_opts.allow_read_network)
assert(configured_opts.allow_write_network)

options.allow_read_network = false
Nokogiri::XSLT.default_security_options = options
configured_opts = Nokogiri::XSLT.default_security_options
refute(configured_opts.allow_read_file)
refute(configured_opts.allow_write_file)
refute(configured_opts.allow_create_directory)
refute(configured_opts.allow_read_network)
assert(configured_opts.allow_write_network)

options.allow_write_network = false
Nokogiri::XSLT.default_security_options = options
configured_opts = Nokogiri::XSLT.default_security_options
refute(configured_opts.allow_read_file)
refute(configured_opts.allow_write_file)
refute(configured_opts.allow_create_directory)
refute(configured_opts.allow_read_network)
refute(configured_opts.allow_write_network)
end
end

def test_exslt
# see http://yokolet.blogspot.com/2010/10/pure-java-nokogiri-xslt-extension.html")
skip_unless_libxml2("cannot get it working on JRuby")
Expand Down