safe_intern 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c83f69632b7a1ac53c90593819f74884dd14d723
4
+ data.tar.gz: 239e8d074b08c60a7959c48f7e87eb7e2628e333
5
+ SHA512:
6
+ metadata.gz: 5901bf0d86098b00d30bcb688d72dd4c14757e7b2105a8c56ab06b8776536fd9b7a3b4b12fe72eac9b1b19052fd55736a2f8fb553f1e6cf20bd55597e885f9f6
7
+ data.tar.gz: 10f41a368bb3f9078c9dde6e8abc5def0439a24b93062f39d22472504c62b77913cc4c1d5caa53866630d3c11466e831ed7065e7ef6c3bd8537f702131744d20
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :development,:test do
4
+ gem 'rake-compiler'
5
+ gem 'rake'
6
+ gem 'rspec'
7
+ gem 'rubocop'
8
+ end
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Jan Rusnacko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,116 @@
1
+ # SafeIntern
2
+
3
+ Safe implemetation of String#intern and String#to_sym methods.
4
+
5
+ ## Description
6
+
7
+ One of the common security issues of Ruby applications is calling `intern` or
8
+ `to_sym` on strings from untrusted source. Since symbols are not garbage
9
+ collected in Ruby, yet take up some memory, this allows attacker to mount
10
+ Denial of Service attack. Just by feeding the application with large number of
11
+ strings and creating large number of symbols, memory consumption of the Ruby
12
+ process would grow till machine runs out of memory.
13
+
14
+ One approach to solving this problem is to prevent calls to .intern on
15
+ untrusted strings by whitelisting expected inputs:
16
+
17
+ whitelist(untrusted_string).to_sym
18
+
19
+ This gets tedious very quickly - developer has to know all of the permitted
20
+ values beforehand and has to maintain the list.
21
+
22
+ Another approach is to allow conversion from string to symbol, but prevent
23
+ allocation of new (previously unseen) symbol. This works most of the time since
24
+ all useful symbols are already allocated in application when its source is
25
+ parsed.
26
+
27
+ ## How it works
28
+
29
+ Symbols are stored in a structure `global_symbols` which maintains mapping
30
+ between frozen strings that are the "name" of symbol and numeric IDs, which
31
+ are used by Ruby interpreter. All currently allocated Symbols can be listed
32
+ by calling `Symbol.all_symbols`.
33
+
34
+ Starting from Ruby 2.0, API provides `rb_check_id` function, which allows to
35
+ query `global_symbols` for particular symbol without allocating it. This is
36
+ much more efficient than doing something like
37
+
38
+ Symbol.all_symbols.map(&:to_s).include?(untrusted_string)
39
+
40
+ With the ability to query for known symbols, `intern` and `to_sym` methods can
41
+ be patched. There are two possible behaviours when called on unknown string:
42
+
43
+ * return nil
44
+ * raise exception
45
+
46
+ This gem provides implementation of both in `SafeIntern::ExceptionPatch` and
47
+ `SafeIntern::NilPatch` modules.
48
+
49
+ ## Usage
50
+
51
+ 1. Install using gem command:
52
+
53
+ $ gem install safe_intern
54
+
55
+ or include in Gemfile:
56
+
57
+ gem 'safe_intern'
58
+
59
+ 2. Patch the String:
60
+
61
+ require 'safe_intern/string'
62
+
63
+ This will patch `String#intern` and `String#to_sym` methods to throw
64
+ exception when new Symbol would be allocated. To return nil instead, patch
65
+ the String with
66
+
67
+ require 'safe_intern'
68
+
69
+ class ::String
70
+ prepend SafeIntern::NilPatch
71
+ end
72
+
73
+ Prepending the `SafeIntern::NilPatch` module is important, since including it
74
+ would result in the wrong order of method lookup.
75
+
76
+ Only particular strings can be patched too:
77
+
78
+ require 'safe_intern'
79
+
80
+ unstrusted_string.extend(SafeIntern::ExceptionPatch)
81
+
82
+ Calling `.to_sym` and `.intern` on unstrusted string that would result in
83
+ new symbol allocation returns nil or throws exception:
84
+
85
+ > untrusted_string.extend(SafeIntern::NilPatch)
86
+ => "does_not_exist"
87
+ > untrusted_string.intern
88
+ => nil
89
+ > untrusted_string.extend(SafeIntern::ExceptionPatch)
90
+ => "does_not_exist"
91
+ > untrusted_string.intern
92
+ UnsafeInternException: UnsafeInternException
93
+ from /home/jrusnack/.gem/ruby/gems/safe_intern-1.0.0/lib/safe_intern/exception_patch.rb:7:in `intern'
94
+ from (irb):4
95
+ from /usr/bin/irb:12:in `<main>'
96
+
97
+ When String class is patched, creating new symbol from trusted string is
98
+ possible with
99
+
100
+ trusted_string.intern(:allow_unsafe)
101
+
102
+ ## Requirements
103
+ * [Ruby] >= 2.0.0
104
+
105
+ ## See also
106
+
107
+ Similar functionality is provided by [Symbol Lookup] gem. Read about addition
108
+ of new method to query for already allocated Symbols in [Issue 7854].
109
+
110
+ ## License
111
+ [SafeIntern] is released under the MIT License.
112
+
113
+ [Ruby]: http://www.ruby-lang.org
114
+ [Symbol Lookup]: https://github.com/phluid61/symbol_lookup-gem
115
+ [Issue 7854]: https://bugs.ruby-lang.org/issues/7854
116
+ [SafeIntern] https://github.com/jrusnack/safe_intern
data/Rakefile ADDED
@@ -0,0 +1,31 @@
1
+ require 'rspec/core/rake_task'
2
+ require 'rake/extensiontask'
3
+ require 'rubocop/rake_task'
4
+ require 'benchmark'
5
+ $:.unshift File.expand_path('../lib', __FILE__)
6
+ require 'safe_intern'
7
+
8
+ RSpec::Core::RakeTask.new
9
+
10
+ Rake::ExtensionTask.new "symbol_defined" do |ext|
11
+ end
12
+
13
+ Rubocop::RakeTask.new
14
+
15
+ desc 'Benchmark implemented methods'
16
+ task :benchmark do
17
+ i = 2000000
18
+
19
+ Benchmark.bm(22) do |bm|
20
+ string = 'String'
21
+ bm.report("unpatched:") { i.times do string.intern end }
22
+
23
+ str = string.clone.extend(SafeIntern::NilPatch)
24
+ bm.report("Nil patch:") { i.times do str.intern end }
25
+ bm.report("Nil patch(allow):") { i.times do str.intern(:allow_unsafe) end }
26
+
27
+ str = string.clone.extend(SafeIntern::ExceptionPatch)
28
+ bm.report("Exception patch:") { i.times do str.intern end }
29
+ bm.report("Exception patch(allow):") { i.times do str.intern(:allow_unsafe) end }
30
+ end
31
+ end
@@ -0,0 +1,8 @@
1
+ # Copyright (C) 2014 Jan Rusnacko
2
+ #
3
+ # This copyrighted material is made available to anyone wishing to use,
4
+ # modify, copy, or redistribute it subject to the terms and conditions of the
5
+ # MIT license.
6
+
7
+ require 'mkmf'
8
+ create_makefile('symbol_defined')
@@ -0,0 +1,26 @@
1
+ /* Copyright (C) 2014 Jan Rusnacko
2
+ *
3
+ * This copyrighted material is made available to anyone wishing to use,
4
+ * modify, copy, or redistribute it subject to the terms and conditions of the
5
+ * MIT license.
6
+ */
7
+
8
+ #include "ruby.h"
9
+
10
+ VALUE
11
+ symbol_defined(VALUE self, VALUE str)
12
+ {
13
+ if(rb_check_id(&str)) {
14
+ return Qtrue;
15
+ } else {
16
+ return Qfalse;
17
+ }
18
+ }
19
+
20
+ VALUE cSafeIntern;
21
+
22
+ void Init_symbol_defined()
23
+ {
24
+ cSafeIntern = rb_define_module("SafeIntern");
25
+ rb_define_module_function(cSafeIntern, "symbol_defined?", symbol_defined, 1);
26
+ }
@@ -0,0 +1,25 @@
1
+ # Copyright (C) 2014 Jan Rusnacko
2
+ #
3
+ # This copyrighted material is made available to anyone wishing to use,
4
+ # modify, copy, or redistribute it subject to the terms and conditions of the
5
+ # MIT license.
6
+
7
+ module SafeIntern
8
+ module ExceptionPatch
9
+ def intern(allow_unsafe = nil)
10
+ if allow_unsafe == :allow_unsafe || SafeIntern.symbol_defined?(self)
11
+ super()
12
+ else
13
+ fail UnsafeInternException
14
+ end
15
+ end
16
+
17
+ def to_sym(allow_unsafe = nil)
18
+ if allow_unsafe == :allow_unsafe || SafeIntern.symbol_defined?(self)
19
+ super()
20
+ else
21
+ fail UnsafeInternException
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ # Copyright (C) 2014 Jan Rusnacko
2
+ #
3
+ # This copyrighted material is made available to anyone wishing to use,
4
+ # modify, copy, or redistribute it subject to the terms and conditions of the
5
+ # MIT license.
6
+
7
+ module SafeIntern
8
+ module NilPatch
9
+ def intern(allow_unsafe = nil)
10
+ if allow_unsafe == :allow_unsafe || SafeIntern.symbol_defined?(self)
11
+ super()
12
+ else
13
+ nil
14
+ end
15
+ end
16
+
17
+ def to_sym(allow_unsafe = nil)
18
+ if allow_unsafe == :allow_unsafe || SafeIntern.symbol_defined?(self)
19
+ super()
20
+ else
21
+ nil
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,16 @@
1
+ # Copyright (C) 2014 Jan Rusnacko
2
+ #
3
+ # This copyrighted material is made available to anyone wishing to use,
4
+ # modify, copy, or redistribute it subject to the terms and conditions of the
5
+ # MIT license.
6
+
7
+ require 'safe_intern'
8
+
9
+ # Patch String class to throw exception when new Symbol is created by intern
10
+ # or to_sym methods.
11
+ #
12
+ # Usage: require 'safe_intern/string'
13
+ #
14
+ class ::String
15
+ prepend SafeIntern::ExceptionPatch
16
+ end
@@ -0,0 +1,7 @@
1
+ # Copyright (C) 2014 Jan Rusnacko
2
+ #
3
+ # This copyrighted material is made available to anyone wishing to use,
4
+ # modify, copy, or redistribute it subject to the terms and conditions of the
5
+ # MIT license.
6
+
7
+ class UnsafeInternException < RuntimeError; end
@@ -0,0 +1,10 @@
1
+ # Copyright (C) 2014 Jan Rusnacko
2
+ #
3
+ # This copyrighted material is made available to anyone wishing to use,
4
+ # modify, copy, or redistribute it subject to the terms and conditions of the
5
+ # MIT license.
6
+
7
+ require 'symbol_defined'
8
+ require 'safe_intern/exception_patch'
9
+ require 'safe_intern/nil_patch'
10
+ require 'safe_intern/unsafe_intern_exception'
@@ -0,0 +1,14 @@
1
+ Gem::Specification.new do |gem|
2
+ gem.name = 'safe_intern'
3
+ gem.version = '1.0.0'
4
+ gem.date = '2014-03-20'
5
+ gem.summary = 'Safe String#intern'
6
+ gem.description = 'Safe implementation of String#intern'
7
+ gem.authors = ["Jan Rusnacko"]
8
+ gem.email = 'rusnackoj@gmail.com'
9
+ gem.files = `git ls-files`.split($/)
10
+ gem.extensions = 'ext/symbol_defined/extconf.rb'
11
+ gem.required_ruby_version = ['>= 2.0']
12
+ gem.homepage = 'https://github.com/jrusnack/safe_intern'
13
+ gem.license = 'MIT'
14
+ end
@@ -0,0 +1,17 @@
1
+ # Copyright (C) 2014 Jan Rusnacko
2
+ #
3
+ # This copyrighted material is made available to anyone wishing to use,
4
+ # modify, copy, or redistribute it subject to the terms and conditions of the
5
+ # MIT license.
6
+
7
+ require 'spec_helper'
8
+
9
+ describe SafeIntern::ExceptionPatch do
10
+ context 'intern' do
11
+ it_behaves_like 'safe-intern', SafeIntern::ExceptionPatch, :intern
12
+ end
13
+
14
+ context 'to_sym' do
15
+ it_behaves_like 'safe-intern', SafeIntern::ExceptionPatch, :to_sym
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # Copyright (C) 2014 Jan Rusnacko
2
+ #
3
+ # This copyrighted material is made available to anyone wishing to use,
4
+ # modify, copy, or redistribute it subject to the terms and conditions of the
5
+ # MIT license.
6
+
7
+ require 'spec_helper'
8
+
9
+ describe SafeIntern::NilPatch do
10
+ context '#intern' do
11
+ it_behaves_like 'safe-intern', SafeIntern::NilPatch, :intern
12
+ end
13
+
14
+ context '#to_sym' do
15
+ it_behaves_like 'safe-intern', SafeIntern::NilPatch, :to_sym
16
+ end
17
+ end
@@ -0,0 +1,29 @@
1
+ # Copyright (C) 2014 Jan Rusnacko
2
+ #
3
+ # This copyrighted material is made available to anyone wishing to use,
4
+ # modify, copy, or redistribute it subject to the terms and conditions of the
5
+ # MIT license.
6
+
7
+ shared_examples 'safe-intern' do |patch, method|
8
+ it 'should convert to Symbol if it already exists' do
9
+ 'Object'.extend(patch).send(method).should be_eql(:Object)
10
+ end
11
+
12
+ it 'should not create new Symbol if it does not already exist' do
13
+ case patch.name
14
+ when 'SafeIntern::NilPatch'
15
+ 'DoesNotExist'.extend(patch).send(method).should be_nil
16
+ when 'SafeIntern::ExceptionPatch'
17
+ expect { 'DoesNotExist'.extend(patch).send(method) }.to raise_error
18
+ else
19
+ fail
20
+ end
21
+ Symbol.all_symbols.map(&:to_s).should_not include('DoesNotExist')
22
+ end
23
+
24
+ it 'should accept :allow_unsafe as optional parameter' do
25
+ rnd = rand(100_000).to_s
26
+ Symbol.all_symbols.map(&:to_s).should_not include(rnd)
27
+ rnd.extend(patch).send(method, :allow_unsafe).should be_eql(rnd.to_sym)
28
+ end
29
+ end
@@ -0,0 +1,13 @@
1
+ # Copyright (C) 2014 Jan Rusnacko
2
+ #
3
+ # This copyrighted material is made available to anyone wishing to use,
4
+ # modify, copy, or redistribute it subject to the terms and conditions of the
5
+ # MIT license.
6
+
7
+ require 'safe_intern'
8
+ require 'safe_intern_helper'
9
+
10
+ RSpec.configure do |config|
11
+ config.color_enabled = true
12
+ config.formatter = :documentation
13
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: safe_intern
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jan Rusnacko
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-03-20 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Safe implementation of String#intern
14
+ email: rusnackoj@gmail.com
15
+ executables: []
16
+ extensions:
17
+ - ext/symbol_defined/extconf.rb
18
+ extra_rdoc_files: []
19
+ files:
20
+ - Gemfile
21
+ - LICENSE
22
+ - README.md
23
+ - Rakefile
24
+ - ext/symbol_defined/extconf.rb
25
+ - ext/symbol_defined/symbol_defined.c
26
+ - lib/safe_intern.rb
27
+ - lib/safe_intern/exception_patch.rb
28
+ - lib/safe_intern/nil_patch.rb
29
+ - lib/safe_intern/string.rb
30
+ - lib/safe_intern/unsafe_intern_exception.rb
31
+ - safe_intern.gemspec
32
+ - spec/safe_intern/exception_patch_spec.rb
33
+ - spec/safe_intern/nil_patch_spec.rb
34
+ - spec/safe_intern_helper.rb
35
+ - spec/spec_helper.rb
36
+ homepage: https://github.com/jrusnack/safe_intern
37
+ licenses:
38
+ - MIT
39
+ metadata: {}
40
+ post_install_message:
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - '>='
47
+ - !ruby/object:Gem::Version
48
+ version: '2.0'
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ requirements: []
55
+ rubyforge_project:
56
+ rubygems_version: 2.2.2
57
+ signing_key:
58
+ specification_version: 4
59
+ summary: Safe String#intern
60
+ test_files: []