bsdcapsicum.rb 0.3.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 +112 -61
- data/bsdcapsicum.rb.gemspec +11 -1
- data/lib/bsd/capsicum/constants.rb +72 -63
- data/lib/bsd/capsicum/ffi.rb +54 -9
- data/lib/bsd/capsicum/version.rb +1 -1
- data/lib/bsd/capsicum.rb +44 -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,65 +52,42 @@ end
|
|
37
52
|
# Error: Not permitted in capability mode @ rb_sysopen - /dev/null (Errno::ECAPMODE)
|
38
53
|
```
|
39
54
|
|
40
|
-
|
41
|
-
|
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:
|
46
|
-
|
47
|
-
```ruby
|
48
|
-
#!/usr/bin/env ruby
|
49
|
-
require "bsd/capsicum"
|
50
|
-
|
51
|
-
print "[parent] In capability mode: ", (BSD::Capsicum.in_capability_mode? ? "yes" : "no"), "\n"
|
52
|
-
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
|
57
|
-
end
|
58
|
-
Process.wait
|
59
|
-
print "[parent] In capability mode: ", (BSD::Capsicum.in_capability_mode? ? "yes" : "no"), "\n"
|
60
|
-
|
61
|
-
##
|
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
|
67
|
-
```
|
68
|
-
|
69
|
-
__Rights__
|
55
|
+
#### File descriptors
|
70
56
|
|
71
57
|
The
|
72
|
-
[BSD::Capsicum
|
73
|
-
method can reduce the capabilities of a file descriptor
|
74
|
-
|
75
|
-
|
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
|
76
65
|
in a child process to allow only read operations. See the
|
77
66
|
[rights(4)](https://man.freebsd.org/cgi/man.cgi?query=rights&apropos=0&sektion=4&format=html)
|
78
|
-
|
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:
|
79
70
|
|
80
71
|
``` ruby
|
81
72
|
#!/usr/bin/env ruby
|
82
73
|
require "bsd/capsicum"
|
74
|
+
require "tmpdir"
|
83
75
|
|
84
|
-
path = File.join(Dir.
|
76
|
+
path = File.join(Dir.tmpdir, "bsdcapsicum.txt")
|
85
77
|
file = File.open(path, File::CREAT | File::TRUNC | File::RDWR)
|
86
78
|
file.sync = true
|
87
|
-
print "[parent] Obtain file descriptor (with
|
79
|
+
print "[parent] Obtain file descriptor (with full capabilities)", "\n"
|
88
80
|
fork do
|
89
|
-
|
90
|
-
print "[
|
81
|
+
file.permit!(:read)
|
82
|
+
print "[child] Reduce capabilities to read", "\n"
|
91
83
|
|
92
84
|
file.gets
|
93
|
-
print "[
|
85
|
+
print "[child] Read OK", "\n"
|
94
86
|
|
95
87
|
begin
|
96
88
|
file.write "foo"
|
97
89
|
rescue Errno::ENOTCAPABLE => ex
|
98
|
-
print "[
|
90
|
+
print "[child] Error: #{ex.message} (#{ex.class})", "\n"
|
99
91
|
end
|
100
92
|
end
|
101
93
|
Process.wait
|
@@ -103,13 +95,58 @@ file.write "[parent] Hello from #{Process.pid}", "\n"
|
|
103
95
|
print "[parent] Write OK", "\n"
|
104
96
|
|
105
97
|
##
|
106
|
-
# [parent] Obtain file descriptor (with
|
107
|
-
# [
|
108
|
-
# [
|
109
|
-
# [
|
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)
|
110
102
|
# [parent] Write OK
|
111
103
|
```
|
112
104
|
|
105
|
+
#### Fcntls
|
106
|
+
|
107
|
+
The
|
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:
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
#!/usr/bin/env ruby
|
117
|
+
require "bsd/capsicum"
|
118
|
+
require "tmpdir"
|
119
|
+
|
120
|
+
path = File.join(Dir.tmpdir, "bsdcapsicum.txt")
|
121
|
+
file = File.open(path, File::CREAT | File::TRUNC | File::RDWR)
|
122
|
+
file.sync = true
|
123
|
+
print "Obtain file descriptor (with full capabilities)", "\n"
|
124
|
+
|
125
|
+
file.permit!(:fcntl)
|
126
|
+
print "Reduce capabilities to fcntl", "\n"
|
127
|
+
|
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"
|
139
|
+
end
|
140
|
+
|
141
|
+
##
|
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)
|
148
|
+
```
|
149
|
+
|
113
150
|
## Documentation
|
114
151
|
|
115
152
|
A complete API reference is available at [0x1eef.github.io/x/bsdcapsicum.rb](https://0x1eef.github.io/x/bsdcapsicum.rb)
|
@@ -122,16 +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)
|
127
164
|
* [git.HardenedBSD.org/@0x1eef](https://git.hardenedbsd.org/0x1eef/bsdcapsicum.rb#about)
|
128
165
|
* [brew.bsd.cafe/@0x1eef](https://brew.bsd.cafe/0x1eef/bsdcapsicum.rb)
|
129
166
|
|
130
167
|
## See also
|
131
168
|
|
132
169
|
* [Freaky/ruby-capsicum](https://github.com/Freaky/ruby-capsicum) <br>
|
133
|
-
bsdcapsicum.rb is a fork of this project.
|
134
|
-
|
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)
|
135
186
|
|
136
187
|
## License
|
137
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,99 @@ 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
|
93
102
|
# @endgroup
|
94
103
|
|
95
104
|
# @group Sizes
|
data/lib/bsd/capsicum/ffi.rb
CHANGED
@@ -5,13 +5,13 @@ module BSD::Capsicum
|
|
5
5
|
require "fiddle"
|
6
6
|
require "fiddle/import"
|
7
7
|
include Fiddle::Types
|
8
|
-
include Constants
|
9
8
|
extend Fiddle::Importer
|
10
9
|
dlload Dir["/lib/libc.*"].first
|
11
10
|
|
12
11
|
extern "int cap_getmode(u_int*)"
|
13
12
|
extern "int cap_enter(void)"
|
14
13
|
extern "int cap_rights_limit(int, const cap_rights_t*)"
|
14
|
+
extern "int cap_fcntls_limit(int, uint32_t)"
|
15
15
|
extern "cap_rights_t* __cap_rights_init(int version, cap_rights_t*, ...)"
|
16
16
|
|
17
17
|
module_function
|
@@ -40,6 +40,17 @@ module BSD::Capsicum
|
|
40
40
|
self["cap_rights_limit"].call(fd, rightsp)
|
41
41
|
end
|
42
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
|
48
|
+
# @return [Integer]
|
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(&:|))
|
52
|
+
end
|
53
|
+
|
43
54
|
##
|
44
55
|
# Provides a Ruby interface for cap_rights_init(2)
|
45
56
|
# @see BSD::Capsicum::Constants See Constants for a full list of capabilities
|
@@ -47,24 +58,58 @@ module BSD::Capsicum
|
|
47
58
|
# A pointer to initialize the `cap_rights_t` structure
|
48
59
|
# @param [Array<Symbol, Integer>] capabilities
|
49
60
|
# An allowed set of capabilities
|
61
|
+
# @raise [TypeError]
|
62
|
+
# When an unknown capability is provided
|
50
63
|
# @return [Fiddle::Pointer]
|
51
64
|
# Returns a pointer to the structure `cap_rights_t`
|
52
65
|
def cap_rights_init(rightsp, *capabilities)
|
66
|
+
cap = Private.cap_lookup(capabilities, Private.cap_all)
|
53
67
|
self["__cap_rights_init"].call(
|
54
68
|
CAP_RIGHTS_VERSION,
|
55
69
|
rightsp,
|
56
|
-
*
|
57
|
-
[ULONG_LONG, cap_all.include?(cap) ? const_get(cap) : cap]
|
58
|
-
}
|
70
|
+
*cap.flat_map { [ULONG_LONG, _1] }
|
59
71
|
)
|
60
72
|
end
|
61
73
|
|
62
74
|
##
|
63
|
-
# @api
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
@
|
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
|
68
113
|
end
|
69
114
|
end
|
70
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,29 +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
47
|
# Limit the capabilities of a file descriptor
|
48
|
-
#
|
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<Symbol, Integer>]
|
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
|
-
|
66
|
-
|
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
|
67
77
|
ensure
|
68
|
-
rightsp
|
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
|
69
95
|
end
|
70
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)
|