pledge 1.1.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7369f71bb2bfdbbea7dd07d98c86f443b8c0a09ddaf13090c6c0a379c2fe6eed
4
- data.tar.gz: bea75bb0d79544311c633aeb04ad7bcb3d2061ace0c6f9f0829e05dd387068e0
3
+ metadata.gz: f794b6f397a93e9eaabdfff4928f49537fb0a45b55c042c7dc27c46d27918f5a
4
+ data.tar.gz: a0291c2d672594938292c7a6a83cc111d3c54679ea412c679e28087b8a870a89
5
5
  SHA512:
6
- metadata.gz: 4031731b34fe1c2497cfa84ffad17b698ada329362cbb0f8730ea2bfa4f094c0429150b92364a8764d04eef1397be3cdb7d70145ccc9ea88b6e0dadb425d36b7
7
- data.tar.gz: 3509a67789e3876149e8880bc031fdb0d5df2bf111cd65b7f112132b1dd64696ff38ef9f70efc854d5ba99eb4c83d1256b4d68ba5805e879725fc5e6ed14a735
6
+ metadata.gz: 9242a1de3c01334a60cb13ab98410dbe533e089228cf1a7a7cfdbb55a6d1ca7614aacbb306974ff85db1165b02a68e292ee9704038e41e9fab5100cccc7b68dc
7
+ data.tar.gz: e51bf3d1b375e5eba599052423fce0438cb21b3810b8dd38d6a26d7543bba2db8e8547edd0b53bbab99c999dfdead8b6f68e88620cfd9368a0c67bfcdf1c3d37
data/CHANGELOG CHANGED
@@ -1,3 +1,11 @@
1
+ === 1.3.0 (2022-12-19)
2
+
3
+ * Allow installation on platforms not supporting pledge, raising NotImplementedError when calling pledge/unveil methods in that case (jcs) (#3)
4
+
5
+ === 1.2.0 (2019-07-07)
6
+
7
+ * Add unveil library and Pledge.unveil for access to unveil(2) to control file system access (jeremyevans)
8
+
1
9
  === 1.1.0 (2019-04-25)
2
10
 
3
11
  * Support execpromises as optional second argument to Pledge.pledge (jeremyevans)
data/README.rdoc CHANGED
@@ -1,17 +1,17 @@
1
1
  = pledge
2
2
 
3
- pledge exposes OpenBSD's pledge(2) system call to ruby, allowing a
4
- program to restrict the types of operations the program
5
- can do after that point. Unlike other similar systems,
6
- pledge is specifically designed for programs that need to
7
- use a wide variety of operations on initialization, but
3
+ pledge exposes OpenBSD's pledge(2) and unveil(2) system
4
+ calls to ruby. pledge(2) allows a program to restrict the
5
+ types of operations the program can do, and unveil(2)
6
+ restricts access to the file system.
7
+
8
+ Unlike other similar systems, pledge and unveil are
9
+ designed for programs that need to use a wide variety of
10
+ operations and file access on initialization, but
8
11
  a fewer number after initialization (when user input will
9
12
  be accepted).
10
13
 
11
- pledge(2) is supported on OpenBSD 5.9+. pledge(2) supports a second
12
- argument for execpromises on OpenBSD 6.3+.
13
-
14
- == Usage
14
+ == pledge
15
15
 
16
16
  First, you need to require the library
17
17
 
@@ -53,8 +53,6 @@ in other classes:
53
53
  Object.send(:include, Pledge)
54
54
  pledge("rpath")
55
55
 
56
- == Options
57
-
58
56
  See the pledge(2) man page for a description of the allowed
59
57
  promises in the strings passed to +Pledge.pledge+.
60
58
 
@@ -63,6 +61,69 @@ promise is added automatically to the current process's promises,
63
61
  as ruby does not function without it, but it is not added to
64
62
  the execpromises (as you can execute non-ruby programs).
65
63
 
64
+ == unveil
65
+
66
+ First, you need to require the library
67
+
68
+ require 'unveil'
69
+
70
+ Then you can use +Pledge.unveil+ as the interface to the unveil(2)
71
+ system call. You pass +Pledge.unveil+ a hash of paths and permissions,
72
+ for those paths, and it calls unveil(2) with the path and permissions
73
+ for each entry.
74
+
75
+ The permissions should be a string with the following characters:
76
+
77
+ r :: Allow read access to existing files and directories
78
+ w :: Allow write access to existing files and directories
79
+ x :: Allow execute access to programs
80
+ c :: Allow create access for new files and directories
81
+
82
+ You can use the empty string as permissions if you want to allow no access
83
+ to the given path, even if you have granted some access to a folder above
84
+ the given folder. You can use a value of +:gem+ to allow read access to
85
+ the directory for the gem specified by the key.
86
+
87
+ +Pledge.unveil+ locks the file system access to the specified paths. If
88
+ you want to specify which paths to allow in multiple places in your
89
+ program, use +Pledge.unveil_without_lock+ for the initial calls and
90
+ +Pledge.unveil+ for the final call.
91
+
92
+ If +Pledge.unveil+ is called with an empty hash, it adds an unveil of +/+
93
+ with no permissions, which denies all access to the file system if
94
+ +unveil_without_lock+ was not called previously with paths.
95
+
96
+ Example:
97
+
98
+ Pledge.unveil(
99
+ '/home/foo/bar' => 'r',
100
+ '/home/foo/bar/data' => 'rwc',
101
+ '/bin' => 'x',
102
+ '/home/foo/bar/secret' => '',
103
+ 'rack' => :gem
104
+ )
105
+
106
+ The value of :gem is mostly mostly needed if the gem uses autoload or
107
+ other forms of runtime requires. This allows read access to
108
+ all files in the gem's folder, not just the gem's require paths,
109
+ so it works correctly for gems that access data (e.g. templates)
110
+ outside of the gem's require paths.
111
+
112
+ If you plan to use pledge and unveil together, you should
113
+ unveil before pledging, unless you use the +unveil+
114
+ promise when pledging.
115
+
116
+ === Issues with unveil and File.realpath
117
+
118
+ +Pledge.unveil+ does not work with +File.realpath+ on Ruby <2.7.
119
+ The Ruby ports officially supported by OpenBSD have had support to
120
+ allow them to work together backported, as long as you are running
121
+ OpenBSD 6.6+ (or 6.5-current after July 2019). As +require+ uses
122
+ +File.realpath+, this means in most cases where you would want to
123
+ use the +:gem+ support, it will not actually work correctly unless
124
+ you are using Ruby 2.7+ or an OpenBSD package with the backported
125
+ support.
126
+
66
127
  == Reporting issues/bugs
67
128
 
68
129
  This library uses GitHub Issues for tracking issues/bugs:
@@ -81,7 +142,7 @@ To get a copy:
81
142
 
82
143
  == Requirements
83
144
 
84
- * OpenBSD 5.9+
145
+ * OpenBSD 5.9+ (6.4+ for unveil, but 6.6+ recommended)
85
146
  * ruby 1.8.7+
86
147
  * rake-compiler (if compiling)
87
148
 
data/Rakefile CHANGED
@@ -1,13 +1,6 @@
1
- require "rake"
2
1
  require "rake/clean"
3
2
 
4
- CLEAN.include %w'**.rbc rdoc'
5
-
6
- desc "Do a full cleaning"
7
- task :distclean do
8
- CLEAN.include %w'tmp pkg tame*.gem lib/*.so'
9
- Rake::Task[:clean].invoke
10
- end
3
+ CLEAN.include %w'lib/*.so tmp coverage'
11
4
 
12
5
  desc "Build the gem"
13
6
  task :package do
@@ -17,12 +10,20 @@ end
17
10
  desc "Run specs"
18
11
  task :spec => :compile do
19
12
  ruby = ENV['RUBY'] ||= FileUtils::RUBY
20
- sh %{#{ruby} spec/pledge_spec.rb}
13
+ sh %{#{ruby} #{"-w" if RUBY_VERSION >= '3'} spec/pledge_spec.rb}
21
14
  end
22
15
 
23
16
  desc "Run specs"
24
17
  task :default => :spec
25
18
 
19
+ desc "Run specs with coverage"
20
+ task :spec_cov => [:compile] do
21
+ ruby = ENV['RUBY'] ||= FileUtils::RUBY
22
+ ENV['COVERAGE'] = '1'
23
+ FileUtils.rm_rf('coverage')
24
+ sh %{#{ruby} spec/unveil_spec.rb}
25
+ end
26
+
26
27
  begin
27
28
  require 'rake/extensiontask'
28
29
  Rake::ExtensionTask.new('pledge')
@@ -1,5 +1,7 @@
1
1
  require 'mkmf'
2
- have_header 'pledge'
2
+ have_header 'unistd.h'
3
+ have_func('pledge')
4
+ have_func('unveil')
3
5
  $CFLAGS << " -O0 -g -ggdb" if ENV['DEBUG']
4
6
  $CFLAGS << " -Wall"
5
7
  create_makefile("pledge")
data/ext/pledge/pledge.c CHANGED
@@ -5,6 +5,7 @@
5
5
  static VALUE ePledgeInvalidPromise;
6
6
  static VALUE ePledgePermissionIncreaseAttempt;
7
7
  static VALUE ePledgeError;
8
+ static VALUE ePledgeUnveilError;
8
9
 
9
10
  static VALUE rb_pledge(int argc, VALUE* argv, VALUE pledge_class) {
10
11
  VALUE promises = Qnil;
@@ -12,6 +13,7 @@ static VALUE rb_pledge(int argc, VALUE* argv, VALUE pledge_class) {
12
13
  const char * prom = NULL;
13
14
  const char * execprom = NULL;
14
15
 
16
+ #ifdef HAVE_PLEDGE
15
17
  rb_scan_args(argc, argv, "11", &promises, &execpromises);
16
18
 
17
19
  if (!NIL_P(promises)) {
@@ -40,10 +42,44 @@ static VALUE rb_pledge(int argc, VALUE* argv, VALUE pledge_class) {
40
42
  rb_raise(ePledgeError, "pledge error");
41
43
  }
42
44
  }
45
+ #else
46
+ rb_raise(rb_eNotImpError, "pledge not supported");
47
+ #endif
43
48
 
44
49
  return Qnil;
45
50
  }
46
51
 
52
+ #ifdef HAVE_UNVEIL
53
+ static VALUE check_unveil(const char * path, const char * perm) {
54
+ if (unveil(path, perm) != 0) {
55
+ switch(errno) {
56
+ case EINVAL:
57
+ rb_raise(ePledgeUnveilError, "invalid permissions value");
58
+ case EPERM:
59
+ rb_raise(ePledgeUnveilError, "attempt to increase permissions, path not accessible, or unveil already locked");
60
+ case E2BIG:
61
+ rb_raise(ePledgeUnveilError, "per-process limit for unveiled paths reached");
62
+ case ENOENT:
63
+ rb_raise(ePledgeUnveilError, "directory in the path does not exist");
64
+ default:
65
+ rb_raise(ePledgeUnveilError, "unveil error");
66
+ }
67
+ }
68
+
69
+ return Qnil;
70
+ }
71
+
72
+ static VALUE rb_unveil(VALUE pledge_class, VALUE path, VALUE perm) {
73
+ SafeStringValue(path);
74
+ SafeStringValue(perm);
75
+ return check_unveil(RSTRING_PTR(path), RSTRING_PTR(perm));
76
+ }
77
+
78
+ static VALUE rb_finalize_unveil(VALUE pledge_class) {
79
+ return check_unveil(NULL, NULL);
80
+ }
81
+ #endif
82
+
47
83
  void Init_pledge(void) {
48
84
  VALUE cPledge;
49
85
  cPledge = rb_define_module("Pledge");
@@ -52,4 +88,10 @@ void Init_pledge(void) {
52
88
  ePledgeError = rb_define_class_under(cPledge, "Error", rb_eStandardError);
53
89
  ePledgeInvalidPromise = rb_define_class_under(cPledge, "InvalidPromise", ePledgeError);
54
90
  ePledgePermissionIncreaseAttempt = rb_define_class_under(cPledge, "PermissionIncreaseAttempt", ePledgeError);
91
+
92
+ #ifdef HAVE_UNVEIL
93
+ rb_define_private_method(cPledge, "_unveil", rb_unveil, 2);
94
+ rb_define_private_method(cPledge, "_finalize_unveil!", rb_finalize_unveil, 0);
95
+ ePledgeUnveilError = rb_define_class_under(cPledge, "UnveilError", rb_eStandardError);
96
+ #endif
55
97
  }
data/lib/unveil.rb ADDED
@@ -0,0 +1,71 @@
1
+ # frozen-string-literal: true
2
+
3
+ require_relative 'pledge'
4
+
5
+ module Pledge
6
+ # Limit access to the file system using unveil(2). +paths+ should be a hash
7
+ # where keys are paths and values are the access permissions for that path. Each
8
+ # value should be a string with the following characters specifying what
9
+ # permissions are allowed:
10
+ #
11
+ # r :: Allow read access to existing files and directories
12
+ # w :: Allow write access to existing files and directories
13
+ # c :: Allow create/delete access for new files and directories
14
+ # x :: Allow execute access to programs
15
+ #
16
+ # You can use the empty string as permissions if you want to allow no access
17
+ # to the given path, even if you have granted some access to a folder above
18
+ # the given folder. You can use a value of +:gem+ to allow read access to
19
+ # the directory for the gem specified by the key.
20
+ #
21
+ # If called with an empty hash, adds an unveil of +/+ with no permissions,
22
+ # which denies all access to the file system if +unveil_without_lock+
23
+ # was not called previously.
24
+ def unveil(paths)
25
+ # :nocov:
26
+ raise NotImplementedError, "unveil not supported" unless Pledge.respond_to?(:_unveil, true)
27
+ # :nocov:
28
+
29
+ if paths.empty?
30
+ paths = {'/'=>''}
31
+ end
32
+
33
+ unveil_without_lock(paths)
34
+ _finalize_unveil!
35
+ end
36
+
37
+ # Same as unveil, but allows for future calls to unveil or unveil_without_lock.
38
+ def unveil_without_lock(paths)
39
+ # :nocov:
40
+ raise NotImplementedError, "unveil not supported" unless Pledge.respond_to?(:_unveil, true)
41
+ # :nocov:
42
+
43
+ paths = Hash[paths]
44
+
45
+ paths.to_a.each do |path, perm|
46
+ unless path.is_a?(String)
47
+ raise UnveilError, "unveil path is not a string: #{path.inspect}"
48
+ end
49
+
50
+ case perm
51
+ when :gem
52
+ unless spec = Gem.loaded_specs[path]
53
+ raise UnveilError, "cannot unveil gem #{path} as it is not loaded"
54
+ end
55
+
56
+ paths.delete(path)
57
+ paths[spec.full_gem_path] = 'r'
58
+ when String
59
+ # nothing to do
60
+ else
61
+ raise UnveilError, "unveil permission is not a string: #{perm.inspect}"
62
+ end
63
+ end
64
+
65
+ paths.each do |path, perm|
66
+ _unveil(path, perm)
67
+ end
68
+
69
+ nil
70
+ end
71
+ end
data/spec/pledge_spec.rb CHANGED
@@ -1,11 +1,4 @@
1
- require './lib/pledge'
2
-
3
- require 'rubygems'
4
- ENV['MT_NO_PLUGINS'] = '1' # Work around stupid autoloading of plugins
5
- gem 'minitest'
6
- require 'minitest/autorun'
7
-
8
- RUBY = ENV['RUBY'] || 'ruby'
1
+ require_relative 'unveil_spec'
9
2
 
10
3
  describe "Pledge.pledge" do
11
4
  def execpledged(promises, execpromises, code)
@@ -41,7 +34,7 @@ describe "Pledge.pledge" do
41
34
  proc{Pledge.pledge("foo")}.must_raise Pledge::InvalidPromise
42
35
  end
43
36
 
44
- it "should raise a Pledge::PermissionIncreaseAttempt if attempting to increase permissinos" do
37
+ it "should raise a Pledge::PermissionIncreaseAttempt if attempting to increase permissions" do
45
38
  pledged("begin; Pledge.pledge('rpath'); rescue Pledge::PermissionIncreaseAttempt; exit 0; end; exit 1")
46
39
  end
47
40
 
@@ -73,8 +66,8 @@ describe "Pledge.pledge" do
73
66
 
74
67
  it "should allow dns lookups if dns is used" do
75
68
  with_lib('socket') do
76
- unpledged("Socket.gethostbyname('google.com')")
77
- pledged("Socket.gethostbyname('google.com')", "dns")
69
+ unpledged("Addrinfo.getaddrinfo('google.com', nil)")
70
+ pledged("Addrinfo.getaddrinfo('google.com', nil)", "dns")
78
71
  end
79
72
  end
80
73
 
@@ -121,9 +114,9 @@ describe "Pledge.pledge" do
121
114
  end
122
115
 
123
116
  it "should handle both promises and execpromises arguments" do
124
- execpledged("proc exec rpath", "stdio rpath", "exit(`cat MIT-LICENSE` == File.read('MIT-LICENSE') ? 0 : 1)").must_equal true
125
- execpledged("proc exec", "stdio rpath", "$stderr.reopen('/dev/null', 'w'); exit(`cat MIT-LICENSE` == File.read('MIT-LICENSE') ? 0 : 1)").must_equal false
126
- execpledged("proc exec rpath", "stdio", "$stderr.reopen('/dev/null', 'w'); exit(`cat MIT-LICENSE` == File.read('MIT-LICENSE') ? 0 : 1)").must_equal false
117
+ execpledged("proc exec rpath", "stdio rpath", "exit(`cat MIT-LICENSE` == File.read('MIT-LICENSE'))").must_equal true
118
+ execpledged("proc exec", "stdio rpath", "$stderr.reopen('/dev/null', 'w'); exit(`cat MIT-LICENSE` == File.read('MIT-LICENSE'))").must_equal false
119
+ execpledged("proc exec rpath", "stdio", "$stderr.reopen('/dev/null', 'w'); exit(`cat MIT-LICENSE` == File.read('MIT-LICENSE'))").must_equal false
127
120
  end
128
121
 
129
122
  it "should handle nil arguments" do
@@ -133,6 +126,6 @@ describe "Pledge.pledge" do
133
126
  execpledged("", nil, "`cat MIT-LICENSE`").must_equal false
134
127
  execpledged(nil, "stdio rpath", "`cat MIT-LICENSE`").must_equal true
135
128
  execpledged(nil, "stdio", "File.read('MIT-LICENSE')").must_equal true
136
- execpledged(nil, "stdio", "$stderr.reopen('/dev/null', 'w'); exit(`cat MIT-LICENSE` == File.read('MIT-LICENSE') ? 0 : 1)").must_equal false
129
+ execpledged(nil, "stdio", "$stderr.reopen('/dev/null', 'w'); exit(`cat MIT-LICENSE` == File.read('MIT-LICENSE'))").must_equal false
137
130
  end
138
131
  end
metadata CHANGED
@@ -1,23 +1,50 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pledge
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-04-25 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2022-12-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: minitest-global_expectations
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake-compiler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
13
41
  description: |
14
- pledge exposes OpenBSD's pledge(2) system call to ruby, allowing a
15
- program to restrict the types of operations the program
16
- can do after that point. Unlike other similar systems,
17
- pledge is specifically designed for programs that need to
18
- use a wide variety of operations on initialization, but
19
- a fewer number after initialization (when user input will
20
- be accepted).
42
+ pledge exposes OpenBSD's pledge(2) and unveil(2) system calls to Ruby, allowing
43
+ a program to restrict the types of operations the program can do, and the file
44
+ system access the program has, after the point of call. Unlike other similar
45
+ systems, pledge and unveil are specifically designed for programs that need to
46
+ use a wide variety of operations on initialization, but a fewer number after
47
+ initialization (when user input will be accepted).
21
48
  email: code@jeremyevans.net
22
49
  executables: []
23
50
  extensions:
@@ -33,18 +60,19 @@ files:
33
60
  - Rakefile
34
61
  - ext/pledge/extconf.rb
35
62
  - ext/pledge/pledge.c
63
+ - lib/unveil.rb
36
64
  - spec/pledge_spec.rb
37
65
  homepage: https://github.com/jeremyevans/ruby-pledge
38
66
  licenses:
39
67
  - MIT
40
68
  metadata: {}
41
- post_install_message:
69
+ post_install_message:
42
70
  rdoc_options:
43
71
  - "--quiet"
44
72
  - "--line-numbers"
45
73
  - "--inline-source"
46
74
  - "--title"
47
- - 'pledge: restrict system operations on OpenBSD'
75
+ - 'pledge: restrict system operations and file system access on OpenBSD'
48
76
  - "--main"
49
77
  - README.rdoc
50
78
  require_paths:
@@ -53,15 +81,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
53
81
  requirements:
54
82
  - - ">="
55
83
  - !ruby/object:Gem::Version
56
- version: 1.8.7
84
+ version: 1.9.2
57
85
  required_rubygems_version: !ruby/object:Gem::Requirement
58
86
  requirements:
59
87
  - - ">="
60
88
  - !ruby/object:Gem::Version
61
89
  version: '0'
62
90
  requirements: []
63
- rubygems_version: 3.0.3
64
- signing_key:
91
+ rubygems_version: 3.3.26
92
+ signing_key:
65
93
  specification_version: 4
66
- summary: Restrict system operations on OpenBSD
94
+ summary: Restrict system operations and file system access on OpenBSD
67
95
  test_files: []