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 +7 -0
- data/Gemfile +8 -0
- data/LICENSE +21 -0
- data/README.md +116 -0
- data/Rakefile +31 -0
- data/ext/symbol_defined/extconf.rb +8 -0
- data/ext/symbol_defined/symbol_defined.c +26 -0
- data/lib/safe_intern/exception_patch.rb +25 -0
- data/lib/safe_intern/nil_patch.rb +25 -0
- data/lib/safe_intern/string.rb +16 -0
- data/lib/safe_intern/unsafe_intern_exception.rb +7 -0
- data/lib/safe_intern.rb +10 -0
- data/safe_intern.gemspec +14 -0
- data/spec/safe_intern/exception_patch_spec.rb +17 -0
- data/spec/safe_intern/nil_patch_spec.rb +17 -0
- data/spec/safe_intern_helper.rb +29 -0
- data/spec/spec_helper.rb +13 -0
- metadata +60 -0
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
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,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
|
data/lib/safe_intern.rb
ADDED
@@ -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'
|
data/safe_intern.gemspec
ADDED
@@ -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
|
data/spec/spec_helper.rb
ADDED
@@ -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: []
|