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 +4 -4
- data/README.md +115 -62
- data/bsdcapsicum.rb.gemspec +11 -1
- data/lib/bsd/capsicum/constants.rb +76 -63
- data/lib/bsd/capsicum/ffi.rb +78 -31
- data/lib/bsd/capsicum/version.rb +1 -1
- data/lib/bsd/capsicum.rb +45 -14
- data/share/examples/bsdcapsicum.rb/1_capability_mode_example.rb +19 -0
- data/share/examples/bsdcapsicum.rb/2_fork_example.rb +19 -0
- data/share/examples/bsdcapsicum.rb/3_set_rights_example.rb +31 -0
- data/share/examples/bsdcapsicum.rb/4_fcntl_example.rb +33 -0
- metadata +21 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 876adbe8c5b993392a5c0827d7de2cb90729a4e6084007be54c611e121efc938
|
4
|
+
data.tar.gz: 85effbf22c9f5f89ebb59478d17211d997ffa12bcd53a37c63b1b0989bf145d1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
15
|
+
### BSD::Capsicum
|
16
|
+
|
17
|
+
#### Capability mode
|
9
18
|
|
10
19
|
A process can enter into capability mode by calling
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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.
|
24
|
-
|
25
|
-
print "
|
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
|
-
|
55
|
+
#### File descriptors
|
41
56
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
-
|
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
|
-
|
54
|
-
print "[
|
55
|
-
|
56
|
-
|
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
|
-
|
94
|
+
file.write "[parent] Hello from #{Process.pid}", "\n"
|
95
|
+
print "[parent] Write OK", "\n"
|
60
96
|
|
61
97
|
##
|
62
|
-
# [parent]
|
63
|
-
# [
|
64
|
-
# [
|
65
|
-
# [
|
66
|
-
# [parent]
|
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
|
-
|
105
|
+
#### Fcntls
|
70
106
|
|
71
107
|
The
|
72
|
-
[BSD::Capsicum
|
73
|
-
method can
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
-
```
|
115
|
+
```ruby
|
81
116
|
#!/usr/bin/env ruby
|
82
117
|
require "bsd/capsicum"
|
118
|
+
require "tmpdir"
|
83
119
|
|
84
|
-
path = File.join(Dir.
|
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 "
|
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
|
-
|
93
|
-
|
125
|
+
file.permit!(:fcntl)
|
126
|
+
print "Reduce capabilities to fcntl", "\n"
|
94
127
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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
|
-
#
|
107
|
-
#
|
108
|
-
#
|
109
|
-
#
|
110
|
-
#
|
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
|
-
* [
|
126
|
-
* [
|
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.
|
132
|
-
|
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
|
|
data/bsdcapsicum.rb.gemspec
CHANGED
@@ -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.
|
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 =
|
16
|
-
CAP_WRITE =
|
17
|
-
CAP_SEEK =
|
18
|
-
CAP_PREAD =
|
19
|
-
CAP_PWRITE =
|
20
|
-
CAP_MMAP =
|
21
|
-
CAP_CREATE =
|
22
|
-
CAP_FEXECVE =
|
23
|
-
CAP_FSYNC =
|
24
|
-
CAP_FTRUNCATE =
|
25
|
-
CAP_FCHFLAGS =
|
26
|
-
CAP_FCHMOD =
|
27
|
-
CAP_FCHMODAT =
|
28
|
-
CAP_FCHOWN =
|
29
|
-
CAP_FCHOWNAT =
|
30
|
-
CAP_FLOCK =
|
31
|
-
CAP_FPATHCONF =
|
32
|
-
CAP_FSTAT =
|
33
|
-
CAP_FSTATAT =
|
34
|
-
CAP_FSTATFS =
|
35
|
-
CAP_FUTIMES =
|
36
|
-
CAP_FUTIMESAT =
|
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 =
|
42
|
-
CAP_BIND =
|
43
|
-
CAP_CONNECT =
|
44
|
-
CAP_GETPEERNAME =
|
45
|
-
CAP_GETSOCKNAME =
|
46
|
-
CAP_GETSOCKOPT =
|
47
|
-
CAP_LISTEN =
|
48
|
-
CAP_PEELOFF =
|
49
|
-
CAP_RECV =
|
50
|
-
CAP_SEND =
|
51
|
-
CAP_SETSOCKOPT =
|
52
|
-
CAP_SHUTDOWN =
|
53
|
-
CAP_BINDAT =
|
54
|
-
CAP_SOCK_CLIENT =
|
55
|
-
CAP_SOCK_SERVER =
|
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 =
|
61
|
-
CAP_ACL_DELETE =
|
62
|
-
CAP_ACL_GET =
|
63
|
-
CAP_ACL_SET =
|
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 =
|
69
|
-
CAP_PDKILL =
|
70
|
-
CAP_PDWAIT =
|
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 =
|
76
|
-
CAP_EVENT =
|
77
|
-
CAP_IOCTL =
|
78
|
-
CAP_KQUEUE =
|
79
|
-
CAP_LOOKUP =
|
80
|
-
CAP_MAC_GET =
|
81
|
-
CAP_MAC_SET =
|
82
|
-
CAP_MKDIRAT =
|
83
|
-
CAP_MKFIFOAT =
|
84
|
-
CAP_MKNODAT =
|
85
|
-
CAP_SEM_GETVALUE =
|
86
|
-
CAP_SEM_POST =
|
87
|
-
CAP_SEM_WAIT =
|
88
|
-
CAP_TTYHOOK =
|
89
|
-
CAP_UNLINKAT =
|
90
|
-
CAP_FSCK =
|
91
|
-
CAP_FCHDIR =
|
92
|
-
CAP_FCNTL =
|
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
|
data/lib/bsd/capsicum/ffi.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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]
|
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
|
40
|
-
|
41
|
-
|
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
|
-
# @
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
)
|
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
|
64
|
-
|
65
|
-
|
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
|
data/lib/bsd/capsicum/version.rb
CHANGED
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
|
-
#
|
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
|
-
# #
|
53
|
-
# BSD::Capsicum.
|
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<
|
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
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
-
|
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.
|
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:
|
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.
|
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)
|