bsdcapsicum.rb 0.2.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5d83ead7ffc798a1957614930e629c867be433fc59a7138327ccae20e2bb35d4
4
- data.tar.gz: 9b91b67471e6cd11ebc3eced51face9e8cae9f96136763666e110b175c4a8f55
3
+ metadata.gz: 876adbe8c5b993392a5c0827d7de2cb90729a4e6084007be54c611e121efc938
4
+ data.tar.gz: 85effbf22c9f5f89ebb59478d17211d997ffa12bcd53a37c63b1b0989bf145d1
5
5
  SHA512:
6
- metadata.gz: 315f60aad74e7db220c09cedc03fa4072fbd483de4cf851c6351f0df599249fc7752d892fc304636f4ca08d80735c88f7bc366854d4323f582d7406760c9c40a
7
- data.tar.gz: ab6a6c0b337f9ddcbf98e97053379bba9c585d51ecc02b1cd72f55ef9add3be5bcf26d3d0da310d939230a9fb2bf66a483f3accefe94e3d4ad8c729efdefbc76
6
+ metadata.gz: 3112c288332876b7c959fa45d214c4be5f1b6dd4bf14ba1ec499dc97d017e2ea6f5d95122be24433c636a6228dc292aa1760b33b8a16dcd0b878217f15032a4e
7
+ data.tar.gz: baa07bdb4999eff23b9b3c27c695d428c63bf9a4c50395439a8b807e8aa810a906432af230f8d1aa8187ffa85b5c03f6e6b20862819e525168dbcf45c244d377
data/README.md CHANGED
@@ -1,28 +1,43 @@
1
1
  ## About
2
2
 
3
- bsdcapsicum.rb provides Ruby bindings for
4
- [capsicum(4)](https://man.freebsd.org/cgi/man.cgi?query=capsicum&apropos=0&sektion=4&format=html).
3
+ bsdcapsicum.rb provides Ruby bindings for FreeBSD's
4
+ [capsicum(4)](https://man.freebsd.org/cgi/man.cgi?query=capsicum&apropos=0&sektion=4&format=html)
5
+ via
6
+ [fiddle](https://github.com/ruby/fiddle#readme). The capsicum framework
7
+ provides a sandbox model where a process can enter into a mode of operation
8
+ where it is exclusively capable of performing system calls on file descriptors
9
+ that have been acquired before entering capability mode. The file descriptors
10
+ can also be limited to a subset of system calls, and a file descriptor could
11
+ reference a file, a socket, or other IO objects.
5
12
 
6
13
  ## Examples
7
14
 
8
- __Capability mode__
15
+ ### BSD::Capsicum
16
+
17
+ #### Capability mode
9
18
 
10
19
  A process can enter into capability mode by calling
11
- [BSD::Capsicum.enter!](http://0x1eef.github.io/x/bsdcapsicum.rb/BSD/Capsicum.html#enter!-instance_method).
12
- After entering capability mode, the process has limited
13
- abilities. File descriptors acquired before entering into
14
- capability mode remain accessible and unrestricted, but
15
- their capabilites can be reduced. See the
20
+ the
21
+ [BSD::Capsicum.enter_capability_mode!](http://0x1eef.github.io/x/bsdcapsicum.rb/BSD/Capsicum.html#enter!-instance_method)
22
+ method. After entering capability mode, a process may only
23
+ issue system calls operating on file descriptors that have already
24
+ been acquired or by reading limited global system state.
25
+ File descriptors acquired before entering capability mode remain
26
+ fully capable but their capabilities can be reduced by calling
27
+ the
28
+ [BSD::Capsicum.permit!](http://0x1eef.github.io/x/bsdcapsicum.rb/BSD/Capsicum.html#permit!-instance_method)
29
+ method. See the
16
30
  [cap_enter(2)](https://man.freebsd.org/cgi/man.cgi?query=cap_enter&apropos=0&sektion=2&format=html)
17
- manual page for more details:
31
+ manual page or the rest of the README for more details:
18
32
 
19
33
  ```ruby
20
34
  #!/usr/bin/env ruby
21
35
  require "bsd/capsicum"
22
36
 
23
- print "In capability mode: ", BSD::Capsicum.in_capability_mode? ? "yes" : "no", "\n"
24
- print "Enter capability mode: ", BSD::Capsicum.enter! ? "ok" : "error", "\n"
25
- print "In capability mode: ", BSD::Capsicum.in_capability_mode? ? "yes" : "no", "\n"
37
+ print "In capability mode: ", (BSD::Capsicum.capability_mode? ? "yes" : "no"), "\n"
38
+ BSD::Capsicum.enter_capability_mode!
39
+ print "Enter capability mode: ok", "\n"
40
+ print "In capability mode: ", (BSD::Capsicum.capability_mode? ? "yes" : "no"), "\n"
26
41
 
27
42
  begin
28
43
  File.new(File::NULL)
@@ -37,77 +52,99 @@ end
37
52
  # Error: Not permitted in capability mode @ rb_sysopen - /dev/null (Errno::ECAPMODE)
38
53
  ```
39
54
 
40
- __IPC__
55
+ #### File descriptors
41
56
 
42
- By spawning a child process and then entering capability mode, restrictions can be
43
- limited to a child process (and its child processes, if any). This can be helpful in
44
- an architecture where a parent process can spawn one or more child processes to handle
45
- certain tasks but with restrictions in place:
57
+ The
58
+ [BSD::Capsicum::IO#permit!](http://0x1eef.github.io/x/bsdcapsicum.rb/BSD/Capsicum/IO.html#permit!-instance_method)
59
+ method can reduce the capabilities of a file descriptor by limiting what
60
+ system calls it can be used with. In that sense it is roughly similar to OpenBSD's
61
+ pledge but it operates on the file descriptor level rather than the process
62
+ level.
63
+ The following example obtains a file descriptor in a parent process (with
64
+ full capabilities), then limits the capabilities of the file descriptor
65
+ in a child process to allow only read operations. See the
66
+ [rights(4)](https://man.freebsd.org/cgi/man.cgi?query=rights&apropos=0&sektion=4&format=html)
67
+ and
68
+ [cap_rights_limit(2)](https://man.freebsd.org/cgi/man.cgi?query=cap_rights_limit&sektion=2&format=htmlman)
69
+ manual pages for more information:
46
70
 
47
- ```ruby
71
+ ``` ruby
48
72
  #!/usr/bin/env ruby
49
73
  require "bsd/capsicum"
74
+ require "tmpdir"
50
75
 
51
- print "[parent] In capability mode: ", BSD::Capsicum.in_capability_mode? ? "yes" : "no", "\n"
76
+ path = File.join(Dir.tmpdir, "bsdcapsicum.txt")
77
+ file = File.open(path, File::CREAT | File::TRUNC | File::RDWR)
78
+ file.sync = true
79
+ print "[parent] Obtain file descriptor (with full capabilities)", "\n"
52
80
  fork do
53
- print "[subprocess] Enter capability mode: ", BSD::Capsicum.enter! ? "ok" : "error", "\n"
54
- print "[subprocess] In capability mode: ", BSD::Capsicum.in_capability_mode? ? "yes" : "no", "\n"
55
- print "[subprocess] Exit", "\n"
56
- exit 42
81
+ file.permit!(:read)
82
+ print "[child] Reduce capabilities to read", "\n"
83
+
84
+ file.gets
85
+ print "[child] Read OK", "\n"
86
+
87
+ begin
88
+ file.write "foo"
89
+ rescue Errno::ENOTCAPABLE => ex
90
+ print "[child] Error: #{ex.message} (#{ex.class})", "\n"
91
+ end
57
92
  end
58
93
  Process.wait
59
- print "[parent] In capability mode: ", BSD::Capsicum.in_capability_mode? ? "yes" : "no", "\n"
94
+ file.write "[parent] Hello from #{Process.pid}", "\n"
95
+ print "[parent] Write OK", "\n"
60
96
 
61
97
  ##
62
- # [parent] In capability mode: no
63
- # [subprocess] Enter capability mode: ok
64
- # [subprocess] In capability mode: yes
65
- # [subprocess] Exit
66
- # [parent] In capability mode: no
98
+ # [parent] Obtain file descriptor (with full capabilities)
99
+ # [child] Reduce capabilities to read
100
+ # [child] Read OK
101
+ # [child] Error: Capabilities insufficient @ io_write - /tmp/bsdcapsicum.txt (Errno::ENOTCAPABLE)
102
+ # [parent] Write OK
67
103
  ```
68
104
 
69
- __Rights__
105
+ #### Fcntls
70
106
 
71
107
  The
72
- [BSD::Capsicum.set_rights!](http://0x1eef.github.io/x/bsdcapsicum.rb/BSD/Capsicum.html#set_rights!-instance_method)
73
- method can reduce the capabilities of a file descriptor. The following
74
- example obtains a file descriptor in a parent process (with both read and
75
- write permissions), then limits the capabilities of the file descriptor
76
- in a child process to allow only read operations. See the
77
- [rights(4)](https://man.freebsd.org/cgi/man.cgi?query=rights&apropos=0&sektion=4&format=html)
78
- man page for a full list of capabilities:
108
+ [BSD::Capsicum::IO#permit!](http://0x1eef.github.io/x/bsdcapsicum.rb/BSD/Capsicum/IO.html#permit!-instance_method)
109
+ method can limit the fcntls capabilities of a file descriptor by limiting what
110
+ fcntls operations it can be used with. This method requires the fcntl capability to already
111
+ be present, and it can limit fnctls operations to a smaller subset of operations.
112
+ The following example limits the fcntls capabilities of a file descriptor to allow
113
+ only the `GETFL` operation, and prevents the `SETFL` operation:
79
114
 
80
- ``` ruby
115
+ ```ruby
81
116
  #!/usr/bin/env ruby
82
117
  require "bsd/capsicum"
118
+ require "tmpdir"
83
119
 
84
- path = File.join(Dir.home, "bsdcapsicum.txt")
120
+ path = File.join(Dir.tmpdir, "bsdcapsicum.txt")
85
121
  file = File.open(path, File::CREAT | File::TRUNC | File::RDWR)
86
122
  file.sync = true
87
- print "[parent] obtain file descriptor (with read+write permissions)", "\n"
88
- fork do
89
- BSD::Capsicum.set_rights!(file, %i[CAP_READ])
90
- print "[subprocess] reduce rights to read-only", "\n"
123
+ print "Obtain file descriptor (with full capabilities)", "\n"
91
124
 
92
- file.gets
93
- print "[subprocess] read successful", "\n"
125
+ file.permit!(:fcntl)
126
+ print "Reduce capabilities to fcntl", "\n"
94
127
 
95
- begin
96
- file.write "foo"
97
- rescue Errno::ENOTCAPABLE => ex
98
- print "[subprocess] Error: #{ex.message} (#{ex.class})", "\n"
99
- end
128
+ file.permit!(:GETFL, scope: :fcntl)
129
+ print "Reduces fcntl capabilties to GETFL", "\n"
130
+
131
+ flags = file.fcntl(Fcntl::F_GETFL)
132
+ print "Get fcntl flags: OK", "\n"
133
+
134
+ begin
135
+ print "Try to set fcntls flag ... ", "\n"
136
+ file.fcntl(Fcntl::F_SETFL, flags | Fcntl::O_APPEND)
137
+ rescue Errno::ENOTCAPABLE => ex
138
+ print "Error: #{ex.message} (#{ex.class})", "\n"
100
139
  end
101
- Process.wait
102
- file.write "[parent] Hello from #{Process.pid}", "\n"
103
- print "[parent] write successful", "\n"
104
140
 
105
141
  ##
106
- # [parent] obtain file descriptor (with read+write permissions)
107
- # [subprocess] reduce rights to read-only
108
- # [subprocess] read successful
109
- # [subprocess] Error: Capabilities insufficient @ io_write - /home/user/bsdcapsicum.txt (Errno::ENOTCAPABLE)
110
- # [parent] write successful
142
+ # Obtain file descriptor (with full capabilities)
143
+ # Reduce capabilities to fcntl
144
+ # Reduce fcntl capabilties to fcntl_getfl
145
+ # Get fcntl flags: OK
146
+ # Try to set fcntls flag ...
147
+ # Error: Capabilities insufficient @ finish_narg - /tmp/bsdcapsicum.txt (Errno::ENOTCAPABLE)
111
148
  ```
112
149
 
113
150
  ## Documentation
@@ -122,14 +159,30 @@ bsdcapsicum.rb is available via rubygems.org:
122
159
 
123
160
  ## Sources
124
161
 
125
- * [GitHub](https://github.com/0x1eef/bsdcapsicum.rb#readme)
126
- * [git.HardenedBSD.org](https://git.hardenedbsd.org/0x1eef/bsdcapsicum.rb#about)
162
+ * [github.com/@0x1eef](https://github.com/0x1eef/bsdcapsicum.rb#readme)
163
+ * [gitlab.com/@0x1eef](https://gitlab.com/0x1eef/bsdcapsicum.rb#about)
164
+ * [git.HardenedBSD.org/@0x1eef](https://git.hardenedbsd.org/0x1eef/bsdcapsicum.rb#about)
165
+ * [brew.bsd.cafe/@0x1eef](https://brew.bsd.cafe/0x1eef/bsdcapsicum.rb)
127
166
 
128
167
  ## See also
129
168
 
130
169
  * [Freaky/ruby-capsicum](https://github.com/Freaky/ruby-capsicum) <br>
131
- bsdcapsicum.rb is a fork of this project. It was a huge help both
132
- in terms of code and documentation.
170
+ bsdcapsicum.rb is a fork of this project.
171
+
172
+ ## Status
173
+
174
+ The following functions have an equivalent Ruby interface:
175
+
176
+ * [x] [cap_enter(2)](https://man.freebsd.org/cgi/man.cgi?query=cap_enter&apropos=0&sektion=2&format=html)
177
+ * [x] [cap_getmode(2)](https://man.freebsd.org/cgi/man.cgi?query=cap_getmode&apropos=0&sektion=2&format=html)
178
+ * [x] [cap_rights_limit(2)](https://man.freebsd.org/cgi/man.cgi?query=cap_rights_limit&sektion=2&format=html)
179
+ * [x] [cap_fcntls_limit(2)](https://man.freebsd.org/cgi/man.cgi?query=cap_fcntls_limit&sektion=2&format=html)
180
+
181
+ The following functions complement
182
+ [cap_rights_limit(2)](https://man.freebsd.org/cgi/man.cgi?query=cap_rights_limit&sektion=2&format=html)
183
+ but have not yet been implemented:
184
+
185
+ * [ ] [cap_ioctls_limit(2)](https://man.freebsd.org/cgi/man.cgi?query=cap_ioctls_limit&sektion=2&format=html)
133
186
 
134
187
  ## License
135
188
 
@@ -12,12 +12,22 @@ Gem::Specification.new do |spec|
12
12
  spec.summary = "Ruby bindings for FreeBSD's capsicum(4)"
13
13
  spec.homepage = "https://github.com/0x1eef/bsdcapsicum.rb"
14
14
  spec.licenses = ["0BSD", "MIT"]
15
- spec.files = Dir["lib/*.rb", "lib/**/*.rb", "README.md", "LICENSE", "LICENSE.ruby-capsicum", "*.gemspec"]
15
+ spec.executables = []
16
16
  spec.require_paths = ["lib"]
17
+ spec.files = Dir[
18
+ "*.gemspec",
19
+ "README.md",
20
+ "LICENSE",
21
+ "LICENSE.ruby-capsicum",
22
+ "lib/*.rb",
23
+ "lib/**/*.rb",
24
+ "share/**/*.rb",
25
+ ].select { File.file?(_1) }
17
26
 
18
27
  spec.add_runtime_dependency "fiddle", "~> 1.1"
19
28
  spec.add_development_dependency "bundler", "~> 2.5"
20
29
  spec.add_development_dependency "rake", "~> 13.2"
30
+ spec.add_development_dependency "rake-compiler", "~> 1.2"
21
31
  spec.add_development_dependency "minitest", "~> 5.0"
22
32
  spec.add_development_dependency "standard", "~> 1.38"
23
33
  spec.add_development_dependency "test-cmd.rb", "~> 0.12"
@@ -6,90 +6,103 @@ module BSD::Capsicum
6
6
  # by sys/capsicum.h and sys/caprights.h. Their
7
7
  # documentation can be found in the
8
8
  # [rights(4)](https://man.freebsd.org/cgi/man.cgi?query=rights&apropos=0&sektion=4&format=html)
9
- # man page
9
+ # man page, and they can be used with methods
10
+ # such as {BSD::Capsicum#limit! BSD::Capsicum#limit!}
10
11
  module Constants
11
12
  CAP_RIGHTS_VERSION = 0x0
12
13
 
13
14
  ##
14
15
  # @group File capabilties
15
- CAP_READ = 0x200000000000001
16
- CAP_WRITE = 0x200000000000002
17
- CAP_SEEK = 0x20000000000000c
18
- CAP_PREAD = 0x20000000000000d
19
- CAP_PWRITE = 0x20000000000000e
20
- CAP_MMAP = 0x200000000000010
21
- CAP_CREATE = 0x200000000000040
22
- CAP_FEXECVE = 0x200000000000080
23
- CAP_FSYNC = 0x200000000000100
24
- CAP_FTRUNCATE = 0x200000000000200
25
- CAP_FCHFLAGS = 0x200000000001000
26
- CAP_FCHMOD = 0x200000000002000
27
- CAP_FCHMODAT = 0x200000000002400
28
- CAP_FCHOWN = 0x200000000004000
29
- CAP_FCHOWNAT = 0x200000000004400
30
- CAP_FLOCK = 0x200000000010000
31
- CAP_FPATHCONF = 0x200000000020000
32
- CAP_FSTAT = 0x200000000080000
33
- CAP_FSTATAT = 0x200000000080400
34
- CAP_FSTATFS = 0x200000000100000
35
- CAP_FUTIMES = 0x200000000200000
36
- CAP_FUTIMESAT = 0x200000000200400
16
+ CAP_READ = FFI::CAP_READ
17
+ CAP_WRITE = FFI::CAP_WRITE
18
+ CAP_SEEK = FFI::CAP_SEEK
19
+ CAP_PREAD = FFI::CAP_PREAD
20
+ CAP_PWRITE = FFI::CAP_PWRITE
21
+ CAP_MMAP = FFI::CAP_MMAP
22
+ CAP_CREATE = FFI::CAP_CREATE
23
+ CAP_FEXECVE = FFI::CAP_FEXECVE
24
+ CAP_FSYNC = FFI::CAP_FSYNC
25
+ CAP_FTRUNCATE = FFI::CAP_FTRUNCATE
26
+ CAP_FCHFLAGS = FFI::CAP_FCHFLAGS
27
+ CAP_FCHMOD = FFI::CAP_FCHMOD
28
+ CAP_FCHMODAT = FFI::CAP_FCHMODAT
29
+ CAP_FCHOWN = FFI::CAP_FCHOWN
30
+ CAP_FCHOWNAT = FFI::CAP_FCHOWNAT
31
+ CAP_FLOCK = FFI::CAP_FLOCK
32
+ CAP_FPATHCONF = FFI::CAP_FPATHCONF
33
+ CAP_FSTAT = FFI::CAP_FSTAT
34
+ CAP_FSTATAT = FFI::CAP_FSTATAT
35
+ CAP_FSTATFS = FFI::CAP_FSTATFS
36
+ CAP_FUTIMES = FFI::CAP_FUTIMES
37
+ CAP_FUTIMESAT = FFI::CAP_FUTIMESAT
37
38
  # @endgroup
38
39
 
39
40
  ##
40
41
  # @group Socket capabilities
41
- CAP_ACCEPT = 0x200000020000000
42
- CAP_BIND = 0x200000040000000
43
- CAP_CONNECT = 0x200000080000000
44
- CAP_GETPEERNAME = 0x200000100000000
45
- CAP_GETSOCKNAME = 0x200000200000000
46
- CAP_GETSOCKOPT = 0x200000400000000
47
- CAP_LISTEN = 0x200000800000000
48
- CAP_PEELOFF = 0x200001000000000
49
- CAP_RECV = CAP_READ
50
- CAP_SEND = CAP_WRITE
51
- CAP_SETSOCKOPT = 0x200002000000000
52
- CAP_SHUTDOWN = 0x200004000000000
53
- CAP_BINDAT = 0x200008000000400
54
- CAP_SOCK_CLIENT = 0x200007780000003
55
- CAP_SOCK_SERVER = 0x200007f60000003
42
+ CAP_ACCEPT = FFI::CAP_ACCEPT
43
+ CAP_BIND = FFI::CAP_BIND
44
+ CAP_CONNECT = FFI::CAP_CONNECT
45
+ CAP_GETPEERNAME = FFI::CAP_GETPEERNAME
46
+ CAP_GETSOCKNAME = FFI::CAP_GETSOCKNAME
47
+ CAP_GETSOCKOPT = FFI::CAP_GETSOCKOPT
48
+ CAP_LISTEN = FFI::CAP_LISTEN
49
+ CAP_PEELOFF = FFI::CAP_PEELOFF
50
+ CAP_RECV = FFI::CAP_RECV
51
+ CAP_SEND = FFI::CAP_SEND
52
+ CAP_SETSOCKOPT = FFI::CAP_SETSOCKOPT
53
+ CAP_SHUTDOWN = FFI::CAP_SHUTDOWN
54
+ CAP_BINDAT = FFI::CAP_BINDAT
55
+ CAP_SOCK_CLIENT = FFI::CAP_SOCK_CLIENT
56
+ CAP_SOCK_SERVER = FFI::CAP_SOCK_SERVER
56
57
  # @endgroup
57
58
 
58
59
  ##
59
60
  # @group ACL capabilities
60
- CAP_ACL_CHECK = 0x400000000010000
61
- CAP_ACL_DELETE = 0x400000000020000
62
- CAP_ACL_GET = 0x400000000040000
63
- CAP_ACL_SET = 0x400000000080000
61
+ CAP_ACL_CHECK = FFI::CAP_ACL_CHECK
62
+ CAP_ACL_DELETE = FFI::CAP_ACL_DELETE
63
+ CAP_ACL_GET = FFI::CAP_ACL_GET
64
+ CAP_ACL_SET = FFI::CAP_ACL_SET
64
65
  # @endgroup
65
66
 
66
67
  ##
67
68
  # @group Process capabilities
68
- CAP_PDGETPID = 0x400000000000200
69
- CAP_PDKILL = 0x400000000000800
70
- CAP_PDWAIT = 0x400000000000400
69
+ CAP_PDGETPID = FFI::CAP_PDGETPID
70
+ CAP_PDKILL = FFI::CAP_PDKILL
71
+ CAP_PDWAIT = FFI::CAP_PDWAIT
72
+ # @endgroup
73
+
74
+ ##
75
+ # @group Fcntl capabilities
76
+ CAP_FCNTL_GETFL = FFI::CAP_FCNTL_GETFL
77
+ CAP_FCNTL_SETFL = FFI::CAP_FCNTL_SETFL
78
+ CAP_FCNTL_GETOWN = FFI::CAP_FCNTL_GETOWN
79
+ CAP_FCNTL_SETOWN = FFI::CAP_FCNTL_SETOWN
71
80
  # @endgroup
72
81
 
73
82
  ##
74
83
  # @group Uncategorized capabilities
75
- CAP_CHFLAGSAT = 0x200000000001400
76
- CAP_EVENT = 0x400000000000020
77
- CAP_IOCTL = 0x400000000000080
78
- CAP_KQUEUE = 0x400000000100040
79
- CAP_LOOKUP = 0x200000000000400
80
- CAP_MAC_GET = 0x400000000000001
81
- CAP_MAC_SET = 0x400000000000002
82
- CAP_MKDIRAT = 0x200000000800400
83
- CAP_MKFIFOAT = 0x200000001000400
84
- CAP_MKNODAT = 0x200000002000400
85
- CAP_SEM_GETVALUE = 0x400000000000004
86
- CAP_SEM_POST = 0x400000000000008
87
- CAP_SEM_WAIT = 0x400000000000010
88
- CAP_TTYHOOK = 0x400000000000100
89
- CAP_UNLINKAT = 0x200000010000400
90
- CAP_FSCK = 0x200000000040000
91
- CAP_FCHDIR = 0x200000000000800
92
- CAP_FCNTL = 0x200000000008000
84
+ CAP_CHFLAGSAT = FFI::CAP_CHFLAGSAT
85
+ CAP_EVENT = FFI::CAP_EVENT
86
+ CAP_IOCTL = FFI::CAP_IOCTL
87
+ CAP_KQUEUE = FFI::CAP_KQUEUE
88
+ CAP_LOOKUP = FFI::CAP_LOOKUP
89
+ CAP_MAC_GET = FFI::CAP_MAC_GET
90
+ CAP_MAC_SET = FFI::CAP_MAC_SET
91
+ CAP_MKDIRAT = FFI::CAP_MKDIRAT
92
+ CAP_MKFIFOAT = FFI::CAP_MKFIFOAT
93
+ CAP_MKNODAT = FFI::CAP_MKNODAT
94
+ CAP_SEM_GETVALUE = FFI::CAP_SEM_GETVALUE
95
+ CAP_SEM_POST = FFI::CAP_SEM_POST
96
+ CAP_SEM_WAIT = FFI::CAP_SEM_WAIT
97
+ CAP_TTYHOOK = FFI::CAP_TTYHOOK
98
+ CAP_UNLINKAT = FFI::CAP_UNLINKAT
99
+ CAP_FSCK = FFI::CAP_FSCK
100
+ CAP_FCHDIR = FFI::CAP_FCHDIR
101
+ CAP_FCNTL = FFI::CAP_FCNTL
102
+ # @endgroup
103
+
104
+ # @group Sizes
105
+ SIZEOF_CAP_RIGHTS_T = 16
93
106
  # @endgroup
94
107
  end
95
108
  end
@@ -3,8 +3,16 @@
3
3
  module BSD::Capsicum
4
4
  module FFI
5
5
  require "fiddle"
6
+ require "fiddle/import"
6
7
  include Fiddle::Types
7
- include Constants
8
+ extend Fiddle::Importer
9
+ dlload Dir["/lib/libc.*"].first
10
+
11
+ extern "int cap_getmode(u_int*)"
12
+ extern "int cap_enter(void)"
13
+ extern "int cap_rights_limit(int, const cap_rights_t*)"
14
+ extern "int cap_fcntls_limit(int, uint32_t)"
15
+ extern "cap_rights_t* __cap_rights_init(int version, cap_rights_t*, ...)"
8
16
 
9
17
  module_function
10
18
 
@@ -12,11 +20,7 @@ module BSD::Capsicum
12
20
  # Provides a Ruby interface for cap_enter(2)
13
21
  # @return [Integer]
14
22
  def cap_enter
15
- Fiddle::Function.new(
16
- libc["cap_enter"],
17
- [],
18
- INT
19
- ).call
23
+ self["cap_enter"].call
20
24
  end
21
25
 
22
26
  ##
@@ -24,45 +28,88 @@ module BSD::Capsicum
24
28
  # @param [Fiddle::Pointer] uintp
25
29
  # @return [Integer]
26
30
  def cap_getmode(uintp)
27
- Fiddle::Function.new(
28
- libc["cap_getmode"],
29
- [INTPTR_T],
30
- INT
31
- ).call(uintp)
31
+ self["cap_getmode"].call(uintp)
32
32
  end
33
33
 
34
34
  ##
35
35
  # Provides a Ruby interface for cap_rights_limit(2)
36
36
  # @param [Integer] fd
37
- # @param [Fiddle::Pointer] rights
37
+ # @param [Fiddle::Pointer] rightsp
38
+ # @return [Integer]
39
+ def cap_rights_limit(fd, rightsp)
40
+ self["cap_rights_limit"].call(fd, rightsp)
41
+ end
42
+
43
+ ##
44
+ # Provides a Ruby interface for cap_fcntls_limit(2)
45
+ # @param [Integer] fd
46
+ # @param [Array<Integer>] capabilities
47
+ # An allowed set of capabilities
38
48
  # @return [Integer]
39
- def cap_rights_limit(fd, rights)
40
- Fiddle::Function.new(
41
- libc["cap_rights_limit"],
42
- [INT, VOIDP],
43
- INT
44
- ).call(fd, rights)
49
+ def cap_fcntls_limit(fd, capabilities)
50
+ cap = Private.cap_lookup(capabilities, Private.cap_fcntls)
51
+ self["cap_fcntls_limit"].call(fd, cap.inject(&:|))
45
52
  end
46
53
 
47
54
  ##
48
55
  # Provides a Ruby interface for cap_rights_init(2)
49
- # @param [Array<Integer>] rights
56
+ # @see BSD::Capsicum::Constants See Constants for a full list of capabilities
57
+ # @param [Fiddle::Pointer] rightsp
58
+ # A pointer to initialize the `cap_rights_t` structure
59
+ # @param [Array<Symbol, Integer>] capabilities
60
+ # An allowed set of capabilities
61
+ # @raise [TypeError]
62
+ # When an unknown capability is provided
50
63
  # @return [Fiddle::Pointer]
51
- def cap_rights_init(*rights)
52
- voidp = Fiddle::Pointer.malloc(Fiddle::SIZEOF_VOIDP)
53
- varargs = rights.flat_map { [ULONG_LONG, (Symbol === _1) ? Constants.const_get(_1) : _1] }
54
- Fiddle::Function.new(
55
- libc["__cap_rights_init"],
56
- [INT, VOIDP, VARIADIC],
57
- VOIDP
58
- ).call(CAP_RIGHTS_VERSION, voidp, *varargs)
59
- voidp
64
+ # Returns a pointer to the structure `cap_rights_t`
65
+ def cap_rights_init(rightsp, *capabilities)
66
+ cap = Private.cap_lookup(capabilities, Private.cap_all)
67
+ self["__cap_rights_init"].call(
68
+ CAP_RIGHTS_VERSION,
69
+ rightsp,
70
+ *cap.flat_map { [ULONG_LONG, _1] }
71
+ )
60
72
  end
61
73
 
62
74
  ##
63
- # @api private
64
- def libc
65
- @libc ||= Fiddle.dlopen Dir["/lib/libc.*"].first
75
+ # @api pricate
76
+ module Private
77
+ extend self
78
+ ##
79
+ # @api private
80
+ # @return [Array<Integer>]
81
+ # Returns a list of capabilities (as integers)
82
+ def cap_lookup(capabilities, allowed)
83
+ capabilities.flat_map do |cap|
84
+ if Integer === cap
85
+ cap
86
+ elsif allowed.include?(cap)
87
+ FFI.const_get(cap)
88
+ elsif allowed.include?(:"CAP_#{cap.upcase}")
89
+ FFI.const_get(:"CAP_#{cap.upcase}")
90
+ elsif allowed.include?(:"CAP_FCNTL_#{cap.upcase}")
91
+ FFI.const_get(:"CAP_FCNTL_#{cap.upcase}")
92
+ else
93
+ raise TypeError, "unknown capability: #{cap}"
94
+ end
95
+ end
96
+ end
97
+
98
+ ##
99
+ # @api private
100
+ # @return [Array<Symbol>]
101
+ # Returns all known capabilities
102
+ def cap_all
103
+ @cap_all ||= Constants.constants.select { _1.to_s.start_with?("CAP_") }
104
+ end
105
+
106
+ ##
107
+ # @api private
108
+ # @return [Array<Symbol>]
109
+ # Returns all known fcntl capabilities
110
+ def cap_fcntls
111
+ @cap_fcntls ||= Constants.constants.select { _1.to_s.start_with?("CAP_FCNTL_") }
112
+ end
66
113
  end
67
114
  end
68
115
  private_constant :FFI
@@ -4,5 +4,5 @@ module BSD
4
4
  end unless defined?(BSD)
5
5
 
6
6
  module BSD::Capsicum
7
- VERSION = "0.2.0"
7
+ VERSION = "0.4.0"
8
8
  end
data/lib/bsd/capsicum.rb CHANGED
@@ -4,14 +4,14 @@ module BSD
4
4
  end unless defined?(BSD)
5
5
 
6
6
  module BSD::Capsicum
7
+ require "bsdcapsicum.rb.so"
7
8
  require_relative "capsicum/version"
8
- require_relative "capsicum/constants"
9
9
  require_relative "capsicum/ffi"
10
+ require_relative "capsicum/constants"
10
11
  extend self
11
12
 
12
13
  ##
13
14
  # Check if we're in capability mode
14
- #
15
15
  # @see https://man.freebsd.org/cgi/man.cgi?query=cap_getmode&apropos=0&sektion=2&format=html cap_getmode(2)
16
16
  # @raise [SystemCallError]
17
17
  # Might raise a subclass of SystemCallError
@@ -31,7 +31,6 @@ module BSD::Capsicum
31
31
 
32
32
  ##
33
33
  # Enter a process into capability mode
34
- #
35
34
  # @see https://man.freebsd.org/cgi/man.cgi?query=cap_enter&apropos=0&sektion=2&format=html cap_enter(2)
36
35
  # @raise [SystemCallError]
37
36
  # Might raise a subclass of SystemCallError
@@ -42,28 +41,60 @@ module BSD::Capsicum
42
41
  raise(SystemCallError.new("cap_enter", Fiddle.last_error))
43
42
  end
44
43
  alias_method :enter_capability_mode!, :enter!
44
+ alias_method :enter_cap_mode!, :enter!
45
45
 
46
46
  ##
47
- # Restrict the capabilities of a file descriptor
48
- #
47
+ # Limit the capabilities of a file descriptor
49
48
  # @see https://man.freebsd.org/cgi/man.cgi?query=cap_rights_limit&apropos=0&sektion=2&format=html cap_rights_limit(2)
50
49
  # @see BSD::Capsicum::Constants See Constants for a full list of capabilities
51
50
  # @example
52
- # # Restrict capabilities of STDOUT to read / write
53
- # BSD::Capsicum.set_rights!(STDOUT, %i[CAP_READ CAP_WRITE])
51
+ # # Permit standard output operations to read and write
52
+ # BSD::Capsicum.permit!(STDOUT, :CAP_READ, :CAP_WRITE)
53
+ # # Ditto
54
+ # BSD::Capsicum.permit!(STDOUT, :read, :write)
54
55
  # @raise [SystemCallError]
55
56
  # Might raise a subclass of SystemCallError
56
- # @param [#to_i] io
57
+ # @param [#fileno,#to_i] io
57
58
  # An IO object
58
- # @param [Array<String>] rights
59
+ # @param [Array<Symbol, Integer>] caps
59
60
  # An allowed set of capabilities
61
+ # @param [Symbol] scope
62
+ # The scope of the permit, either `nil` or `:fcntl`
60
63
  # @return [Boolean]
61
64
  # Returns true when successful
62
- def set_rights!(io, rights)
63
- voidp = FFI.cap_rights_init(*rights)
64
- FFI.cap_rights_limit(io.to_i, voidp).zero? ||
65
- raise(SystemCallError.new("cap_rights_limit", Fiddle.last_error))
65
+ def permit!(io, *caps, scope: :rights)
66
+ if scope == :fcntl
67
+ FFI.cap_fcntls_limit(io.to_i, caps).zero? ||
68
+ raise(SystemCallError.new("cap_fcntls_limit", Fiddle.last_error))
69
+ elsif scope == :rights
70
+ rightsp = Fiddle::Pointer.malloc(Constants::SIZEOF_CAP_RIGHTS_T)
71
+ FFI.cap_rights_init(rightsp, *caps)
72
+ FFI.cap_rights_limit(io.to_i, rightsp).zero? ||
73
+ raise(SystemCallError.new("cap_rights_limit", Fiddle.last_error))
74
+ else
75
+ raise ArgumentError, "invalid scope: #{scope}"
76
+ end
66
77
  ensure
67
- voidp.call_free
78
+ rightsp&.call_free
79
+ end
80
+
81
+ ##
82
+ # This module is included into Ruby's IO class
83
+ module IO
84
+ ##
85
+ # Limit the capabilities of a file descriptor
86
+ # @param [Array<Symbol, Integer>] caps
87
+ # An allowed set of capabilities
88
+ # @param [Symbol] scope
89
+ # The scope of the permit, either `nil` or `:fcntl`
90
+ # @see BSD::Capsicum::Constants See CAP_FCNTLS_* for a full list of capabilities
91
+ # @see BSD::Capsicum::Constants See CAP_* for a full list of capabilities
92
+ def permit!(*caps, scope: :rights)
93
+ BSD::Capsicum.permit!(self, *caps, scope:)
94
+ end
68
95
  end
69
96
  end
97
+
98
+ class IO
99
+ include BSD::Capsicum::IO
100
+ end
@@ -0,0 +1,19 @@
1
+ require "bundler/setup"
2
+ require "bsd/capsicum"
3
+
4
+ print "In capability mode: ", (BSD::Capsicum.capability_mode? ? "yes" : "no"), "\n"
5
+ BSD::Capsicum.enter_capability_mode!
6
+ print "Enter capability mode: ok", "\n"
7
+ print "In capability mode: ", (BSD::Capsicum.capability_mode? ? "yes" : "no"), "\n"
8
+
9
+ begin
10
+ File.new(File::NULL)
11
+ rescue Errno::ECAPMODE => ex
12
+ print "Error: #{ex.message} (#{ex.class})", "\n"
13
+ end
14
+
15
+ ##
16
+ # In capability mode: no
17
+ # Enter capability mode: ok
18
+ # In capability mode: yes
19
+ # Error: Not permitted in capability mode @ rb_sysopen - /dev/null (Errno::ECAPMODE)
@@ -0,0 +1,19 @@
1
+ require "bundler/setup"
2
+ require "bsd/capsicum"
3
+
4
+ print "[parent] In capability mode: ", (BSD::Capsicum.in_capability_mode? ? "yes" : "no"), "\n"
5
+ fork do
6
+ print "[child] Enter capability mode: ", (BSD::Capsicum.enter! ? "ok" : "error"), "\n"
7
+ print "[child] In capability mode: ", (BSD::Capsicum.in_capability_mode? ? "yes" : "no"), "\n"
8
+ print "[child] Exit", "\n"
9
+ exit 42
10
+ end
11
+ Process.wait
12
+ print "[parent] In capability mode: ", (BSD::Capsicum.in_capability_mode? ? "yes" : "no"), "\n"
13
+
14
+ ##
15
+ # [parent] In capability mode: no
16
+ # [child] Enter capability mode: ok
17
+ # [child] In capability mode: yes
18
+ # [child] Exit
19
+ # [parent] In capability mode: no
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+ require "bsd/capsicum"
3
+ require "tmpdir"
4
+
5
+ path = File.join(Dir.tmpdir, "bsdcapsicum.txt")
6
+ file = File.open(path, File::CREAT | File::TRUNC | File::RDWR)
7
+ file.sync = true
8
+ print "[parent] Obtain file descriptor (with full capabilities)", "\n"
9
+ fork do
10
+ file.permit!(:read)
11
+ print "[child] Reduce capabilities to read", "\n"
12
+
13
+ file.gets
14
+ print "[child] Read OK", "\n"
15
+
16
+ begin
17
+ file.write "foo"
18
+ rescue Errno::ENOTCAPABLE => ex
19
+ print "[child] Error: #{ex.message} (#{ex.class})", "\n"
20
+ end
21
+ end
22
+ Process.wait
23
+ file.write "[parent] Hello from #{Process.pid}", "\n"
24
+ print "[parent] Write OK", "\n"
25
+
26
+ ##
27
+ # [parent] Obtain file descriptor (with full capabilities)
28
+ # [child] Reduce capabilities to read
29
+ # [child] Read OK
30
+ # [child] Error: Capabilities insufficient @ io_write - /tmp/bsdcapsicum.txt (Errno::ENOTCAPABLE)
31
+ # [parent] Write OK
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env ruby
2
+ require "bsd/capsicum"
3
+ require "tmpdir"
4
+ require "fcntl"
5
+
6
+ path = File.join(Dir.tmpdir, "bsdcapsicum.txt")
7
+ file = File.open(path, File::CREAT | File::TRUNC | File::RDWR)
8
+ file.sync = true
9
+ print "Obtain file descriptor (with full capabilities)", "\n"
10
+
11
+ file.permit!(:fcntl)
12
+ print "Reduce capabilities to fcntl", "\n"
13
+
14
+ file.permit!(:GETFL, scope: :fcntl)
15
+ print "Reduces fcntl capabilties to GETFL", "\n"
16
+
17
+ flags = file.fcntl(Fcntl::F_GETFL)
18
+ print "Get fcntl flags: OK", "\n"
19
+
20
+ begin
21
+ print "Try to set fcntls flag ... ", "\n"
22
+ file.fcntl(Fcntl::F_SETFL, flags | Fcntl::O_APPEND)
23
+ rescue Errno::ENOTCAPABLE => ex
24
+ print "Error: #{ex.message} (#{ex.class})", "\n"
25
+ end
26
+
27
+ ##
28
+ # Obtain file descriptor (with full capabilities)
29
+ # Reduce capabilities to fcntl
30
+ # Reduce fcntl capabilties to fcntl_getfl
31
+ # Get fcntl flags: OK
32
+ # Try to set fcntls flag ...
33
+ # Error: Capabilities insufficient @ finish_narg - /tmp/bsdcapsicum.txt (Errno::ENOTCAPABLE)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bsdcapsicum.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Hurst
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2024-06-27 00:00:00.000000000 Z
12
+ date: 2025-04-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: fiddle
@@ -53,6 +53,20 @@ dependencies:
53
53
  - - "~>"
54
54
  - !ruby/object:Gem::Version
55
55
  version: '13.2'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rake-compiler
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '1.2'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '1.2'
56
70
  - !ruby/object:Gem::Dependency
57
71
  name: minitest
58
72
  requirement: !ruby/object:Gem::Requirement
@@ -125,6 +139,10 @@ files:
125
139
  - lib/bsd/capsicum/ffi.rb
126
140
  - lib/bsd/capsicum/version.rb
127
141
  - lib/bsdcapsicum.rb
142
+ - share/examples/bsdcapsicum.rb/1_capability_mode_example.rb
143
+ - share/examples/bsdcapsicum.rb/2_fork_example.rb
144
+ - share/examples/bsdcapsicum.rb/3_set_rights_example.rb
145
+ - share/examples/bsdcapsicum.rb/4_fcntl_example.rb
128
146
  homepage: https://github.com/0x1eef/bsdcapsicum.rb
129
147
  licenses:
130
148
  - 0BSD
@@ -145,7 +163,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
145
163
  - !ruby/object:Gem::Version
146
164
  version: '0'
147
165
  requirements: []
148
- rubygems_version: 3.5.11
166
+ rubygems_version: 3.5.23
149
167
  signing_key:
150
168
  specification_version: 4
151
169
  summary: Ruby bindings for FreeBSD's capsicum(4)