cap2 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  Cap2
2
2
  ====
3
3
 
4
- Cap2 is a Ruby library for managing the POSIX 1003.1e capabilities available in Linux kernels. These capabilities are a partitioning of the all powerful root privilege into a set of distinct privileges. See capabilites(7) for more information.
4
+ Cap2 is a Ruby library for managing the POSIX 1003.1e capabilities available in Linux kernels. These capabilities are a partitioning of the all powerful root privilege into a set of distinct privileges. See capabilities(7) for more information.
5
5
 
6
6
  Installation
7
7
  ------------
@@ -32,57 +32,127 @@ $ sudo make install
32
32
  $ gem install cap2
33
33
  ```
34
34
 
35
- Querying Capabilities
36
- ---------------------
35
+ Usage
36
+ -----
37
37
 
38
- ### Processes
38
+ Cap2 provides methods for querying and modifying capabilities for both processes and files.
39
39
 
40
- Suppose a process with pid 1000 exists with the following capabilities:
40
+ Process capabilities are accessed via a `Cap2::Process` object (returned from `Cap2.process`) and file capabilities via a `Cap2::File` object (returned from `Cap2.file`):
41
41
 
42
42
  ```
43
- Permitted - CAP_CHOWN, CAP_LEASE
44
- Effective - CAP_CHOWN
45
- Inheritable - CAP_LEASE
43
+ Cap2.process # Returns a Cap2::Process for the current process
44
+ Cap2.process(1000) # Returns a Cap2::Process for the process with pid 1000
45
+ Cap2.file('/sbin/init') # Returns a Cap2::File for the /sbin/init file
46
46
  ```
47
47
 
48
- Then:
48
+ Capabilities are referenced using lower cased symbols, and without the CAP_ prefix (e.g. `:kill` represents CAP_KILL).
49
+
50
+ ### Querying Capabilities
51
+
52
+ There are three methods - `permitted?`, `effective?` and `inheritable?` - defined on both `Cap2::Process` and `Cap2::File` for querying capabilities. Each take a capability symbol and return true / false if the capability is in / not in the relevant set:
49
53
 
50
54
  ```
51
- process = Cap2.process(1000) # => #<Cap2::Process>
55
+ # the init daemon - all caps permitted & effective but not inheritable
56
+ init = Cap2.process(1) # => #<Cap2::Process @pid=1>
57
+
58
+ init.permitted?(:kill) # => true
59
+ init.permitted?(:chown) # => true
60
+
61
+ init.effective?(:fowner) # => true
62
+
63
+ init.inheritable?(:kill) # => false
64
+ init.inheritable?(:fowner) # => false
65
+
66
+ # assume /bin/ping is using file capabilities to enable CAP_NET_RAW on exec
67
+ ping = Cap2.file('/bin/ping') # => #<Cap2::File @filename="/bin/ping">
68
+
69
+ ping.permitted?(:net_raw) # => true
70
+ ping.permitted?(:mknod) # => false
71
+
72
+ ping.effective?(:net_raw) # => true
73
+
74
+ ping.inheritable?(:net_raw) # => false
75
+ ```
76
+
77
+ ### Modifying Capabilities
78
+
79
+ Cap2 provides different levels of control over process and file capabilities.
80
+
81
+ #### Files
82
+
83
+ To modify the permitted capabilities of a file (i.e. the capabilities which will be permitted in any process that exec's the file), use `Cap2::File#permit` and `Cap2::File#unpermit`:
84
+
85
+ ```
86
+ Cap2.process.effective?(:setfcap) # => true - needed to set file capabilities
87
+
88
+ file = Cap2.file('/tmp/file') # => #<Cap2::File @filename="/tmp/file">
52
89
 
53
- process.permitted?(:chown) # => true
54
- process.permitted?(:lease) # => true
55
- process.permitted?(:fowner) # => false
90
+ file.permitted?(:mknod) # => false
56
91
 
57
- process.effective?(:chown) # => true
58
- process.effective?(:lease) # => false
92
+ file.permit(:mknod) # => true
93
+ file.permitted?(:mknod) # => true
59
94
 
60
- process.inheritable?(:chown) # => false
61
- process.inheritable?(:lease) # => true
95
+ file.unpermit(:mknod) # => true
96
+ file.permitted?(:mknod) # => false
62
97
  ```
63
98
 
64
- ### Files
99
+ To modify the effective capabilities of a file (i.e. the capabilities which will be enabled in any process that exec's the file), use `Cap2::File#enable_on_exec` and `Cap2::File#disable_on_exec`:
65
100
 
66
- Suppose the file "/tmp/cap_test" exists with the following capabilities:
101
+ ```
102
+ Cap2.process.effective?(:setfcap) # => true - needed to set file capabilities
103
+
104
+ file = Cap2.file('/tmp/file') # => #<Cap2::File @filename="/tmp/file">
105
+
106
+ file.effective?(:net_raw) # => false
67
107
 
108
+ file.enable_on_exec(:net_raw) # => true
109
+ file.effective?(:net_raw) # => true
110
+
111
+ file.disable_on_exec(:net_raw) # => true
112
+ file.effective?(:net_raw) # => false
68
113
  ```
69
- Permitted - CAP_CHOWN, CAP_LEASE
70
- Effective - CAP_CHOWN
71
- Inheritable - CAP_LEASE
114
+
115
+ To modify the inheritable capabilities of a file (i.e. the capabilities which will be ANDed with the inheritable set of any process that exec's the file to determine which capabilities are in the permitted set of the process), use `Cap2::File#allow_inherit` and `Cap2::File#disallow_inherit`:
116
+
72
117
  ```
118
+ Cap2.process.effective?(:setfcap) # => true - needed to set file capabilities
119
+
120
+ file = Cap2.file('/tmp/file') # => #<Cap2::File @filename="/tmp/file">
73
121
 
74
- Then:
122
+ file.inheritable?(:fowner) # => false
75
123
 
124
+ file.allow_inherit(:fowner) # => true
125
+ file.inheritable?(:fowner) # => true
126
+
127
+ file.disallow_inherit(:fowner) # => true
128
+ file.inheritable?(:fowner) # => false
76
129
  ```
77
- file = Cap2.file('/tmp/cap_test') # => #<Cap2::File>
78
130
 
79
- file.permitted?(:chown) # => true
80
- file.permitted?(:lease) # => true
81
- file.permitted?(:fowner) # => false
131
+ #### Processes
132
+
133
+ Cap2 can be used to enable / disable capabilities of the current Ruby process.
82
134
 
83
- file.effective?(:chown) # => true
84
- file.effective?(:lease) # => false
135
+ Suppose the ruby binary file permits :kill, but does not enable it on exec:
85
136
 
86
- file.inheritable?(:chown) # => false
87
- file.inheritable?(:lease) # => true
88
137
  ```
138
+ ruby = Cap2.file('/usr/bin/ruby') # => #<Cap2::File @filename="/usr/bin/ruby">
139
+ ruby.permitted?(:kill) # => true
140
+ ruby.effective?(:kill) # => false
141
+ ```
142
+
143
+ and we want to kill a process running as root with pid 1000:
144
+
145
+ ```
146
+ Cap2.process.effective?(:kill) # => false
147
+ Process.kill("TERM", 1000) # => Errno::EPERM: Operation not permitted
148
+
149
+ Cap2.process.enable(:kill) # => true
150
+ Cap2.process.effective?(:kill) # => true
151
+ Process.kill("TERM", 1000) # => 1
152
+ ```
153
+
154
+ Notes
155
+ -----
156
+
157
+ * Cap2 cannot be used to modify capabilities of other processes. If you are curious as to why, see the Notes section of [cap_set_proc(3)](http://linux.die.net/man/3/cap_set_proc) for an explanation.
158
+ * File capabilities are not supported for shell scripts due to security implications, similar to the reasons setuid is disabled, [see here](http://unix.stackexchange.com/questions/364/allow-setuid-on-shell-scripts#answer-2910) for more information.
data/Rakefile CHANGED
@@ -9,3 +9,10 @@ desc 'Run all specs in the spec directory'
9
9
  RSpec::Core::RakeTask.new(:spec => [:clobber, :compile]) do |t|
10
10
  t.pattern = FileList['spec/**/*_spec.rb']
11
11
  end
12
+
13
+ require 'rdoc/task'
14
+
15
+ RDoc::Task.new do |rd|
16
+ rd.title = 'Cap2'
17
+ rd.rdoc_files.include("lib/**/*.rb", "ext/**/*.c")
18
+ end
@@ -1,149 +1,417 @@
1
1
  #include <ruby.h>
2
2
  #include <errno.h>
3
+ #include <unistd.h>
3
4
  #include <sys/capability.h>
4
5
 
5
- static VALUE cap2_has_cap(cap_t cap_d, cap_flag_t set, cap_value_t cap) {
6
+ /*
7
+ * Converts a Ruby symbol into cap_flag_t set, defined in <sys/capability.h>
8
+ *
9
+ * Raises an ArgumentError if set is not a valid capability set
10
+ */
11
+ cap_flag_t cap2_sym_to_set(VALUE set) {
12
+ char *set_s;
13
+
14
+ Check_Type(set, T_SYMBOL);
15
+
16
+ set = rb_sym_to_s(set);
17
+
18
+ set_s = StringValueCStr(set);
19
+
20
+ if(strcmp(set_s, "permitted") == 0) return CAP_PERMITTED;
21
+ else if(strcmp(set_s, "effective") == 0) return CAP_EFFECTIVE;
22
+ else if(strcmp(set_s, "inheritable") == 0) return CAP_INHERITABLE;
23
+ else rb_raise(rb_eArgError, "unknown set %s", set_s);
24
+ }
25
+
26
+ /*
27
+ * Converts a Ruby symbol into cap_value_t capability value, defined
28
+ * in <linux/capability.h>
29
+ *
30
+ * Raises an ArgumentError if cap is not a valid capability value.
31
+ */
32
+ cap_value_t cap2_sym_to_cap(VALUE cap) {
33
+ char *cap_s;
34
+
35
+ Check_Type(cap, T_SYMBOL);
36
+
37
+ cap = rb_sym_to_s(cap);
38
+
39
+ cap_s = StringValueCStr(cap);
40
+
41
+ if(strcmp(cap_s, "chown") == 0) return CAP_CHOWN;
42
+ else if(strcmp(cap_s, "dac_override") == 0) return CAP_DAC_OVERRIDE;
43
+ else if(strcmp(cap_s, "dac_read_search") == 0) return CAP_DAC_READ_SEARCH;
44
+ else if(strcmp(cap_s, "fowner") == 0) return CAP_FOWNER;
45
+ else if(strcmp(cap_s, "fsetid") == 0) return CAP_FSETID;
46
+ else if(strcmp(cap_s, "kill") == 0) return CAP_KILL;
47
+ else if(strcmp(cap_s, "setgid") == 0) return CAP_SETGID;
48
+ else if(strcmp(cap_s, "setuid") == 0) return CAP_SETUID;
49
+ else if(strcmp(cap_s, "setpcap") == 0) return CAP_SETPCAP;
50
+ else if(strcmp(cap_s, "linux_immutable") == 0) return CAP_LINUX_IMMUTABLE;
51
+ else if(strcmp(cap_s, "net_bind_service") == 0) return CAP_NET_BIND_SERVICE;
52
+ else if(strcmp(cap_s, "net_broadcast") == 0) return CAP_NET_BROADCAST;
53
+ else if(strcmp(cap_s, "net_admin") == 0) return CAP_NET_ADMIN;
54
+ else if(strcmp(cap_s, "net_raw") == 0) return CAP_NET_RAW;
55
+ else if(strcmp(cap_s, "ipc_lock") == 0) return CAP_IPC_LOCK;
56
+ else if(strcmp(cap_s, "ipc_owner") == 0) return CAP_IPC_OWNER;
57
+ else if(strcmp(cap_s, "sys_module") == 0) return CAP_SYS_MODULE;
58
+ else if(strcmp(cap_s, "sys_rawio") == 0) return CAP_SYS_RAWIO;
59
+ else if(strcmp(cap_s, "sys_chroot") == 0) return CAP_SYS_CHROOT;
60
+ else if(strcmp(cap_s, "sys_ptrace") == 0) return CAP_SYS_PTRACE;
61
+ else if(strcmp(cap_s, "sys_pacct") == 0) return CAP_SYS_PACCT;
62
+ else if(strcmp(cap_s, "sys_admin") == 0) return CAP_SYS_ADMIN;
63
+ else if(strcmp(cap_s, "sys_boot") == 0) return CAP_SYS_BOOT;
64
+ else if(strcmp(cap_s, "sys_nice") == 0) return CAP_SYS_NICE;
65
+ else if(strcmp(cap_s, "sys_resource") == 0) return CAP_SYS_RESOURCE;
66
+ else if(strcmp(cap_s, "sys_time") == 0) return CAP_SYS_TIME;
67
+ else if(strcmp(cap_s, "sys_tty_config") == 0) return CAP_SYS_TTY_CONFIG;
68
+ else if(strcmp(cap_s, "mknod") == 0) return CAP_MKNOD;
69
+ else if(strcmp(cap_s, "lease") == 0) return CAP_LEASE;
70
+ else if(strcmp(cap_s, "audit_write") == 0) return CAP_AUDIT_WRITE;
71
+ else if(strcmp(cap_s, "audit_control") == 0) return CAP_AUDIT_CONTROL;
72
+ else if(strcmp(cap_s, "setfcap") == 0) return CAP_SETFCAP;
73
+ else if(strcmp(cap_s, "mac_override") == 0) return CAP_MAC_OVERRIDE;
74
+ else if(strcmp(cap_s, "mac_admin") == 0) return CAP_MAC_ADMIN;
75
+ else if(strcmp(cap_s, "syslog") == 0) return CAP_SYSLOG;
76
+ else if(strcmp(cap_s, "wake_alarm") == 0) return CAP_WAKE_ALARM;
77
+ else rb_raise(rb_eArgError, "unknown capability %s", cap_s);
78
+ }
79
+
80
+ /*
81
+ * Returns a boolean representing whether cap_d has the given capability enabled
82
+ * in the given set
83
+ */
84
+ VALUE cap2_has_cap(cap_t cap_d, VALUE set_sym, VALUE cap_sym) {
85
+ cap_flag_t set;
86
+ cap_value_t cap;
6
87
  cap_flag_value_t flag_value = CAP_CLEAR;
7
88
 
89
+ set = cap2_sym_to_set(set_sym);
90
+ cap = cap2_sym_to_cap(cap_sym);
91
+
8
92
  cap_get_flag(cap_d, cap, set, &flag_value);
9
93
 
10
94
  return flag_value == CAP_SET ? Qtrue : Qfalse;
11
95
  }
12
96
 
13
- static VALUE cap2_pid_has_cap(VALUE self, VALUE pid, VALUE set, VALUE cap) {
97
+ /*
98
+ * Convert @pid stored in the given Process object to an int and return it.
99
+ */
100
+ static int cap2_process_pid(VALUE process) {
101
+ VALUE pid;
102
+
103
+ pid = rb_iv_get(process, "@pid");
104
+
105
+ return FIX2INT(pid);
106
+ }
107
+
108
+ /*
109
+ * Return a cap_t struct containing the capabilities of the given Process object.
110
+ */
111
+ static cap_t cap2_process_caps(VALUE process) {
14
112
  cap_t cap_d;
15
- VALUE result;
113
+ int pid;
16
114
 
17
- int p = FIX2INT(pid);
115
+ pid = cap2_process_pid(process);
18
116
 
19
- cap_d = cap_get_pid(p);
117
+ cap_d = cap_get_pid(pid);
20
118
 
21
119
  if (cap_d == NULL) {
22
120
  rb_raise(
23
121
  rb_eRuntimeError,
24
122
  "Failed to get capabilities for proccess %d: (%s)\n",
25
- p, strerror(errno)
123
+ pid, strerror(errno)
26
124
  );
27
125
  }
28
126
 
29
- set = FIX2INT(set);
30
- cap = FIX2INT(cap);
127
+ return cap_d;
128
+ }
129
+
130
+ /*
131
+ * Enable/disable the given capability in the given set for the given Process
132
+ * object.
133
+ */
134
+ static VALUE cap2_process_set_cap(VALUE process, cap_flag_t set, VALUE cap_sym, cap_flag_value_t set_or_clear) {
135
+ cap_t cap_d;
136
+ int pid;
137
+ cap_value_t caps[1];
138
+
139
+ pid = cap2_process_pid(process);
140
+
141
+ if((pid_t) pid != getpid())
142
+ rb_raise(
143
+ rb_eRuntimeError,
144
+ "Cannot set capabilities for other processes"
145
+ );
146
+
147
+ caps[0] = cap2_sym_to_cap(cap_sym);
148
+
149
+ cap_d = cap_get_pid(pid);
150
+
151
+ cap_set_flag(cap_d, set, 1, caps, set_or_clear);
152
+
153
+ if(cap_set_proc(cap_d) == -1) {
154
+ rb_raise(
155
+ rb_eRuntimeError,
156
+ "Failed to set capabilities for process %d: (%s)\n",
157
+ pid, strerror(errno)
158
+ );
159
+ } else {
160
+ return Qtrue;
161
+ }
162
+ }
163
+
164
+ /*
165
+ * call-seq:
166
+ * has?(set, capability) -> true or false
167
+ *
168
+ * Return whether the process has the given capability enabled in the given set.
169
+ *
170
+ * Cap2.process(1).has?(:permitted, :kill) #=> true
171
+ * Cap2.process(1000).has?(:permitted, :kill) #=> false
172
+ */
173
+ VALUE cap2_process_has_cap(VALUE self, VALUE set_sym, VALUE cap_sym) {
174
+ cap_t cap_d;
175
+ VALUE result;
176
+
177
+ cap_d = cap2_process_caps(self);
31
178
 
32
- result = cap2_has_cap(
33
- cap_d,
34
- (cap_flag_t) set,
35
- (cap_value_t) cap
36
- );
179
+ result = cap2_has_cap(cap_d, set_sym, cap_sym);
37
180
 
38
181
  cap_free(cap_d);
39
182
 
40
183
  return result;
41
184
  }
42
185
 
43
- static VALUE cap2_file_has_cap(VALUE self, VALUE filename, VALUE set, VALUE cap) {
186
+ /*
187
+ * call-seq:
188
+ * enable(capability) -> true or false
189
+ *
190
+ * Enable the given capability for this process.
191
+ *
192
+ * Raises a RuntimeError if the process's pid is not the same as the current
193
+ * pid (you cannot enable capabilities for other processes, that's their job).
194
+ *
195
+ * process = Cap2.process #=> <Cap2::Process>
196
+ * process.permitted?(:kill) #=> true
197
+ * process.effective?(:kill) #=> false
198
+ * process.enable(:kill) #=> true
199
+ * process.effective?(:kill) #=> true
200
+ */
201
+ VALUE cap2_process_enable(VALUE self, VALUE cap_sym) {
202
+ return cap2_process_set_cap(self, CAP_EFFECTIVE, cap_sym, CAP_SET);
203
+ }
204
+
205
+ /*
206
+ * call-seq:
207
+ * disable(capability) -> true or false
208
+ *
209
+ * Disable the given capability for this process.
210
+ *
211
+ * process = Cap2.process #=> <Cap2::Process>
212
+ * process.permitted?(:kill) #=> true
213
+ * process.effective?(:kill) #=> true
214
+ * process.disable(:kill) #=> true
215
+ * process.effective?(:kill) #=> false
216
+ */
217
+ VALUE cap2_process_disable(VALUE self, VALUE cap_sym) {
218
+ return cap2_process_set_cap(self, CAP_EFFECTIVE, cap_sym, CAP_CLEAR);
219
+ }
220
+
221
+ /*
222
+ * Convert @filename stored in the given File object to a char* and return it.
223
+ */
224
+ static char *cap2_file_filename(VALUE file) {
225
+ VALUE filename;
226
+
227
+ filename = rb_iv_get(file, "@filename");
228
+
229
+ return StringValueCStr(filename);
230
+ }
231
+
232
+ /*
233
+ * Return a cap_t struct containing the capabilities of the given File object.
234
+ */
235
+ static cap_t cap2_file_caps(VALUE file) {
44
236
  cap_t cap_d;
45
- VALUE result;
237
+ char *filename;
46
238
 
47
- char *f = StringValueCStr(filename);
239
+ filename = cap2_file_filename(file);
48
240
 
49
- cap_d = cap_get_file(f);
241
+ cap_d = cap_get_file(filename);
50
242
 
51
243
  if (cap_d == NULL && errno != ENODATA) {
52
244
  rb_raise(
53
245
  rb_eRuntimeError,
54
246
  "Failed to get capabilities for file %s: (%s)\n",
55
- f, strerror(errno)
247
+ filename, strerror(errno)
248
+ );
249
+ }
250
+
251
+ return cap_d;
252
+ }
253
+
254
+ /*
255
+ * Enable/disable the given capability in the given set for the given File
256
+ * object.
257
+ */
258
+ static VALUE cap2_file_set_cap(VALUE file, cap_flag_t set, VALUE cap_sym, cap_flag_value_t set_or_clear) {
259
+ cap_t cap_d;
260
+ char *filename;
261
+ cap_value_t caps[1];
262
+
263
+ filename = cap2_file_filename(file);
264
+
265
+ caps[0] = cap2_sym_to_cap(cap_sym);
266
+
267
+ cap_d = cap_get_file(filename);
268
+
269
+ if(cap_d == NULL)
270
+ cap_d = cap_init();
271
+
272
+ cap_set_flag(cap_d, set, 1, caps, set_or_clear);
273
+
274
+ if(cap_set_file(filename, cap_d) == -1) {
275
+ rb_raise(
276
+ rb_eRuntimeError,
277
+ "Failed to set capabilities for file %s: (%s)\n",
278
+ filename, strerror(errno)
56
279
  );
280
+ } else {
281
+ return Qtrue;
57
282
  }
283
+ }
58
284
 
59
- set = FIX2INT(set);
60
- cap = FIX2INT(cap);
285
+ /*
286
+ * call-seq:
287
+ * has?(set, capability) -> true or false
288
+ *
289
+ * Return whether the file has the given capability enabled in the given set.
290
+ *
291
+ * Cap2.file('/bin/ping').has?(:permitted, :net_raw) #=> true
292
+ * Cap2.file('/tmp/ping').has?(:permitted, :net_raw) #=> false
293
+ */
294
+ VALUE cap2_file_has_cap(VALUE self, VALUE set_sym, VALUE cap_sym) {
295
+ cap_t cap_d;
296
+ VALUE result;
297
+
298
+ cap_d = cap2_file_caps(self);
61
299
 
62
- result = cap2_has_cap(
63
- cap_d,
64
- (cap_flag_t) set,
65
- (cap_value_t) cap
66
- );
300
+ result = cap2_has_cap(cap_d, set_sym, cap_sym);
67
301
 
68
302
  cap_free(cap_d);
69
303
 
70
304
  return result;
71
305
  }
72
306
 
73
- #define SetsHashSet(key,val) \
74
- rb_hash_aset(sets_hash, rb_str_new2(key), INT2FIX(val))
307
+ /*
308
+ * call-seq:
309
+ * permit(capability) -> true or false
310
+ *
311
+ * Permit processes executing this file to enable the given capability.
312
+ *
313
+ * file = Cap2.file('/tmp/killer') #=> <Cap2::File>
314
+ * file.permitted?(:kill) #=> false
315
+ * file.permit(:kill) #=> true
316
+ * file.permitted?(:kill) #=> true
317
+ */
318
+ VALUE cap2_file_permit(VALUE self, VALUE cap_sym) {
319
+ return cap2_file_set_cap(self, CAP_PERMITTED, cap_sym, CAP_SET);
320
+ }
75
321
 
76
- #define CapsHashSet(key,val) \
77
- rb_hash_aset(caps_hash, rb_str_new2(key), INT2FIX(val))
322
+ /*
323
+ * call-seq:
324
+ * unpermit(capability) -> true or false
325
+ *
326
+ * Dont permit processes executing ths file to enable the given capability.
327
+ *
328
+ * file = Cap2.file('/tmp/foo') #=> <Cap2::File>
329
+ * file.permit(:kill) #=> true
330
+ * file.permitted?(:kill) #=> true
331
+ * file.unpermit(:kill) #=> true
332
+ * file.permitted?(:kill) #=> false
333
+ */
334
+ VALUE cap2_file_unpermit(VALUE self, VALUE cap_sym) {
335
+ return cap2_file_set_cap(self, CAP_PERMITTED, cap_sym, CAP_CLEAR);
336
+ }
78
337
 
338
+ /*
339
+ * call-seq:
340
+ * allow_inherit(capability) -> true or false
341
+ *
342
+ * Allow processes executing this file to inherit the given capability.
343
+ *
344
+ * file = Cap2.file('/tmp/foo') #=> <Cap2::File>
345
+ * file.inheritable?(:kill) #=> false
346
+ * file.allow_inherit(:kill) #=> true
347
+ * file.inheritable?(:kill) #=> true
348
+ */
349
+ VALUE cap2_file_allow_inherit(VALUE self, VALUE cap_sym) {
350
+ return cap2_file_set_cap(self, CAP_INHERITABLE, cap_sym, CAP_SET);
351
+ }
352
+
353
+ /*
354
+ * call-seq:
355
+ * disallow_inherit(capability) -> true or false
356
+ *
357
+ * Dont allow processes executing this file to inherit the given capability.
358
+ *
359
+ * file = Cap2.file('/tmp/foo') #=> <Cap2::File>
360
+ * file.inheritable?(:kill) #=> true
361
+ * file.allow_inherit(:kill) #=> true
362
+ * file.inheritable?(:kill) #=> false
363
+ */
364
+ VALUE cap2_file_disallow_inherit(VALUE self, VALUE cap_sym) {
365
+ return cap2_file_set_cap(self, CAP_INHERITABLE, cap_sym, CAP_CLEAR);
366
+ }
367
+
368
+ /*
369
+ * call-seq:
370
+ * set_effective(capability) -> true or false
371
+ *
372
+ * Enable the given capability when a proces executes this file.
373
+ *
374
+ * file = Cap2.file('/tmp/foo') #=> <Cap2::File>
375
+ * file.effective?(:kill) #=> false
376
+ * file.set_effective(:kill) #=> true
377
+ * file.effective?(:kill) #=> true
378
+ */
379
+ VALUE cap2_file_set_effective(VALUE self, VALUE cap_sym) {
380
+ return cap2_file_set_cap(self, CAP_EFFECTIVE, cap_sym, CAP_SET);
381
+ }
382
+
383
+ /*
384
+ * call-seq:
385
+ * disable_on_exec(capability) -> true or false
386
+ *
387
+ * Dont enable the given capability when a process executes this file.
388
+ *
389
+ * file = Cap2.file('/tmp/foo') #=> <Cap2::File>
390
+ * file.effective?(:kill) #=> true
391
+ * file.disable_on_exec(:kill) #=> true
392
+ * file.effective?(:kill) #=> false
393
+ */
394
+ VALUE cap2_file_clear_effective(VALUE self, VALUE cap_sym) {
395
+ return cap2_file_set_cap(self, CAP_EFFECTIVE, cap_sym, CAP_CLEAR);
396
+ }
79
397
  void Init_cap2(void) {
80
398
  VALUE rb_mCap2;
81
-
82
- // sets_hash and caps_hash act as maps between lower cased
83
- // names of capabilities (e.g. 'dac_override') and the CAP_
84
- // constants defined in linux/capability.h and
85
- // sys/capability.h (e.g. CAP_DAC_OVERRIDE). They are
86
- // assigned to Cap2::SETS and Cap2::CAPS respectively.
87
- VALUE sets_hash, caps_hash;
399
+ VALUE rb_cCap2File;
400
+ VALUE rb_cCap2Process;
88
401
 
89
402
  rb_mCap2 = rb_define_module("Cap2");
90
403
 
91
- sets_hash = rb_hash_new();
92
- SetsHashSet("permitted", CAP_PERMITTED);
93
- SetsHashSet("effective", CAP_EFFECTIVE);
94
- SetsHashSet("inheritable", CAP_INHERITABLE);
95
- rb_define_const(rb_mCap2, "SETS", sets_hash);
96
-
97
- caps_hash = rb_hash_new();
98
- CapsHashSet("chown", CAP_CHOWN);
99
- CapsHashSet("dac_override", CAP_DAC_OVERRIDE);
100
- CapsHashSet("dac_read_search", CAP_DAC_READ_SEARCH);
101
- CapsHashSet("fowner", CAP_FOWNER);
102
- CapsHashSet("fsetid", CAP_FSETID);
103
- CapsHashSet("kill", CAP_KILL);
104
- CapsHashSet("setgid", CAP_SETGID);
105
- CapsHashSet("setuid", CAP_SETUID);
106
- CapsHashSet("setpcap", CAP_SETPCAP);
107
- CapsHashSet("linux_immutable", CAP_LINUX_IMMUTABLE);
108
- CapsHashSet("net_bind_service", CAP_NET_BIND_SERVICE);
109
- CapsHashSet("net_broadcast", CAP_NET_BROADCAST);
110
- CapsHashSet("net_admin", CAP_NET_ADMIN);
111
- CapsHashSet("net_raw", CAP_NET_RAW);
112
- CapsHashSet("ipc_lock", CAP_IPC_LOCK);
113
- CapsHashSet("ipc_owner", CAP_IPC_OWNER);
114
- CapsHashSet("sys_module", CAP_SYS_MODULE);
115
- CapsHashSet("sys_rawio", CAP_SYS_RAWIO);
116
- CapsHashSet("sys_chroot", CAP_SYS_CHROOT);
117
- CapsHashSet("sys_ptrace", CAP_SYS_PTRACE);
118
- CapsHashSet("sys_pacct", CAP_SYS_PACCT);
119
- CapsHashSet("sys_admin", CAP_SYS_ADMIN);
120
- CapsHashSet("sys_boot", CAP_SYS_BOOT);
121
- CapsHashSet("sys_nice", CAP_SYS_NICE);
122
- CapsHashSet("sys_resource", CAP_SYS_RESOURCE);
123
- CapsHashSet("sys_time", CAP_SYS_TIME);
124
- CapsHashSet("sys_tty_config", CAP_SYS_TTY_CONFIG);
125
- CapsHashSet("mknod", CAP_MKNOD);
126
- CapsHashSet("lease", CAP_LEASE);
127
- CapsHashSet("audit_write", CAP_AUDIT_WRITE);
128
- CapsHashSet("audit_control", CAP_AUDIT_CONTROL);
129
- CapsHashSet("setfcap", CAP_SETFCAP);
130
- CapsHashSet("mac_override", CAP_MAC_OVERRIDE);
131
- CapsHashSet("mac_admin", CAP_MAC_ADMIN);
132
- CapsHashSet("syslog", CAP_SYSLOG);
133
- CapsHashSet("wake_alarm", CAP_WAKE_ALARM);
134
- rb_define_const(rb_mCap2, "CAPS", caps_hash);
135
-
136
- rb_define_module_function(
137
- rb_mCap2,
138
- "pid_has_cap?",
139
- cap2_pid_has_cap,
140
- 3
141
- );
142
-
143
- rb_define_module_function(
144
- rb_mCap2,
145
- "file_has_cap?",
146
- cap2_file_has_cap,
147
- 3
148
- );
404
+ rb_cCap2Process = rb_define_class_under(rb_mCap2, "Process", rb_cObject);
405
+ rb_define_method(rb_cCap2Process, "has?", cap2_process_has_cap, 2);
406
+ rb_define_method(rb_cCap2Process, "enable", cap2_process_enable, 1);
407
+ rb_define_method(rb_cCap2Process, "disable", cap2_process_disable, 1);
408
+
409
+ rb_cCap2File = rb_define_class_under(rb_mCap2, "File", rb_cObject);
410
+ rb_define_method(rb_cCap2File, "has?", cap2_file_has_cap, 2);
411
+ rb_define_method(rb_cCap2File, "permit", cap2_file_permit, 1);
412
+ rb_define_method(rb_cCap2File, "unpermit", cap2_file_unpermit, 1);
413
+ rb_define_method(rb_cCap2File, "allow_inherit", cap2_file_allow_inherit, 1);
414
+ rb_define_method(rb_cCap2File, "disallow_inherit", cap2_file_disallow_inherit, 1);
415
+ rb_define_method(rb_cCap2File, "set_effective", cap2_file_set_effective, 1);
416
+ rb_define_method(rb_cCap2File, "disable_on_exec", cap2_file_clear_effective, 1);
149
417
  }
@@ -1,48 +1,15 @@
1
1
  require 'cap2.so'
2
- require 'cap2/entity'
3
2
  require 'cap2/process'
4
3
  require 'cap2/file'
5
4
 
5
+ # Cap2 is a module for querying the POSIX 1003.1e capabilities
6
+ # available in Linux kernels. These capabilities are a
7
+ # partitioning of the all powerful root privilege into a set
8
+ # of distinct privileges.
9
+ #
10
+ # For more information see capabilities(7).
6
11
  module Cap2
7
- # = Cap2
8
- #
9
- # Cap2 is a module for querying the POSIX 1003.1e capabilities
10
- # available in Linux kernels. These capabilities are a
11
- # partitioning of the all powerful root privilege into a set
12
- # of distinct privileges.
13
12
  class << self
14
- # A wrapper function around the native Cap2.pid_has_cap?
15
- # and Cap2.file_has_cap?. Returns true if the given
16
- # capability is enabled in the given set for the given pid /
17
- # filename.
18
- #
19
- # @param [String, Fixnum] pid_or_filename
20
- # When a Fixnum, query the capabilities for the process
21
- # with this value. When a String, query for the file
22
- # with a filename of this value.
23
- #
24
- # @param [Symbol] set
25
- # One of :permitted, :effective or :inheritable.
26
- #
27
- # @param [Symbol] cap
28
- # A lower cased name of a capability, without the 'CAP_'
29
- # prefix. For example, :chown would query the CAP_CHOWN
30
- # capability
31
- def has_capability?(pid_or_filename, set, cap)
32
- # See ext/cap2/cap2.c for the definition of SETS and CAPS
33
- set = SETS[set.to_s]
34
- cap = CAPS[cap.to_s]
35
-
36
- case pid_or_filename
37
- when Fixnum
38
- pid_has_cap?(pid_or_filename, set, cap)
39
- when String
40
- file_has_cap?(pid_or_filename, set, cap)
41
- else
42
- raise ArgumentError, "wrong argument type, expected Fixnum or String, got #{pid_or_filename.class}"
43
- end
44
- end
45
-
46
13
  # Returns a Cap2::Process initialized with the given pid,
47
14
  # defaulting to the current pid.
48
15
  def process(pid = ::Process.pid)
@@ -77,6 +44,12 @@ module Cap2
77
44
  end
78
45
  end
79
46
 
47
+ # Raised when trying to initialise a Process object for a non-existent pid.
80
48
  class ProcessNotFound < StandardError; end
49
+
50
+ # Raised when trying to initialise a File object for a non-existent file.
81
51
  class FileNotFound < StandardError; end
52
+
53
+ # Raised when trying to enable unpermitted / uninheritable file capabilities.
54
+ class IncompatibleCapabilities < StandardError; end
82
55
  end
Binary file
@@ -1,11 +1,26 @@
1
+ require 'cap2/set_methods'
2
+
1
3
  module Cap2
2
- class File < Entity
3
- # = Cap2::File
4
- #
5
- # A class with methods for querying capabilities for the
6
- # file with filename provided to the initialize method.
4
+ # A class with methods for querying capabilities for the
5
+ # file with filename provided to the initialize method.
6
+ class File
7
+ include SetMethods
8
+
9
+ # Initialize a new File object for the given filename.
7
10
  def initialize(filename)
8
- @entity_id = filename
11
+ @filename = filename
12
+ end
13
+
14
+ # Enable the given capability in the file's effective set.
15
+ #
16
+ # The capability must be either permitted or inheritable (or else it cannot
17
+ # possibly be enabled in the new process).
18
+ def enable_on_exec(capability)
19
+ if permitted?(capability) || inheritable?(capability)
20
+ set_effective(capability)
21
+ else
22
+ raise IncompatibleCapabilities, 'cannot enable_on_exec a capability which is neither permitted nor inheritable'
23
+ end
9
24
  end
10
25
  end
11
26
  end
@@ -1,11 +1,14 @@
1
+ require 'cap2/set_methods'
2
+
1
3
  module Cap2
2
- class Process < Entity
3
- # = Cap2::Process
4
- #
5
- # A class with methods for querying capabilities for the
6
- # process with pid provided to the initialize method.
4
+ # A class with methods for querying capabilities for the
5
+ # process with pid provided to the initialize method.
6
+ class Process
7
+ include SetMethods
8
+
9
+ # Initialize a new Process object for the given pid.
7
10
  def initialize(pid)
8
- @entity_id = pid
11
+ @pid = pid
9
12
  end
10
13
  end
11
14
  end
@@ -0,0 +1,26 @@
1
+ module Cap2
2
+ # A mixin for the Cap2::Process and Cap2::File
3
+ # classes providing convenience methods for querying
4
+ # permitted, effective and inheritable capabilities.
5
+ #
6
+ # Each method takes a capability argument, a lower
7
+ # cased name of a capability, without the 'CAP_' prefix.
8
+ # For example, :chown would query the CAP_CHOWN capability.
9
+ module SetMethods
10
+ # Returns whether the given capability is permitted
11
+ def permitted?(capability)
12
+ has?(:permitted, capability)
13
+ end
14
+
15
+ # Returns whether the given capability is effective
16
+ def effective?(capability)
17
+ has?(:effective, capability)
18
+ end
19
+
20
+ # Returns whether the given capability is inheritable
21
+ def inheritable?(capability)
22
+ has?(:inheritable, capability)
23
+ end
24
+ end
25
+ end
26
+
@@ -1,3 +1,3 @@
1
1
  module Cap2
2
- Version = '0.0.1'
2
+ Version = '0.1.0'
3
3
  end
@@ -1,38 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Cap2 do
4
- describe '.has_capability?' do
5
- context 'for processes' do
6
- context 'when the given process does not have the given capability' do
7
- it { should_not have_capability(
8
- Process.pid, :permitted, :dac_override) }
9
- end
10
-
11
- context 'when the given process does have the given capability' do
12
- it { should have_capability(
13
- 1, :permitted, :dac_override) }
14
- end
15
- end
16
-
17
- context 'for files' do
18
- let(:file) { Tempfile.new('cap-test') }
19
-
20
- context 'when the given file does not have the given capability' do
21
- it { should_not have_capability(
22
- file.path, :permitted, :dac_override) }
23
- end
24
-
25
- context 'when the given file does have the given capability' do
26
- before(:each) do
27
- system %{sudo setcap "cap_dac_override+p" #{file.path}}
28
- end
29
-
30
- it { should have_capability(
31
- file.path, :permitted, :dac_override) }
32
- end
33
- end
34
- end
35
-
36
4
  describe '.process' do
37
5
  let(:process) { double 'process' }
38
6
 
@@ -1,6 +1,140 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Cap2::File do
4
- it_behaves_like 'an entity'
5
- end
4
+ let(:file) { Tempfile.new('cap-test') }
5
+
6
+ subject { Cap2::File.new(file.path) }
7
+
8
+ describe '#permitted?' do
9
+ context "when the file doesn't have the given capability" do
10
+ it { should_not be_permitted(:dac_override) }
11
+ end
12
+
13
+ context 'when the process does have the given capability' do
14
+ before(:each) do
15
+ system %{sudo setcap "cap_dac_override+p" #{file.path}}
16
+ end
17
+
18
+ it { should be_permitted(:dac_override) }
19
+ end
20
+ end
21
+
22
+ describe '#effective?' do
23
+ context "when the file doesn't have the given capability" do
24
+ it { should_not be_effective(:dac_override) }
25
+ end
26
+
27
+ context 'when the process does have the given capability' do
28
+ before(:each) do
29
+ system %{sudo setcap "cap_dac_override+pe" #{file.path}}
30
+ end
31
+
32
+ it { should be_effective(:dac_override) }
33
+ end
34
+ end
35
+
36
+ describe '#inheritable?' do
37
+ context "when the file doesn't have the given capability" do
38
+ it { should_not be_inheritable(:dac_override) }
39
+ end
40
+
41
+ context 'when the process does have the given capability' do
42
+ before(:each) do
43
+ system %{sudo setcap "cap_dac_override+i" #{file.path}}
44
+ end
45
+
46
+ it { should be_inheritable(:dac_override) }
47
+ end
48
+ end
49
+
50
+ describe '#permit' do
51
+ specify do
52
+ expect { running_as_root('permit(:fowner)') }.to \
53
+ change { subject.permitted?(:fowner) }.from(false).to(true)
54
+ end
55
+ end
56
+
57
+ describe '#unpermit' do
58
+ before(:each) do
59
+ run_as_root('permit(:fowner)')
60
+ end
6
61
 
62
+ specify do
63
+ expect { running_as_root('unpermit(:fowner)') }.to \
64
+ change { subject.permitted?(:fowner) }.from(true).to(false)
65
+ end
66
+ end
67
+
68
+ describe '#allow_inherit' do
69
+ specify do
70
+ expect { running_as_root('allow_inherit(:chown)') }.to \
71
+ change { subject.inheritable?(:chown) }.from(false).to(true)
72
+ end
73
+ end
74
+
75
+ describe '#disallow_inherit' do
76
+ before(:each) do
77
+ run_as_root('allow_inherit(:chown)')
78
+ end
79
+
80
+ specify do
81
+ expect { running_as_root('disallow_inherit(:chown)') }.to \
82
+ change { subject.inheritable?(:chown) }.from(true).to(false)
83
+ end
84
+ end
85
+
86
+ describe '#enable_on_exec' do
87
+ context 'when the capability is not permitted or inheritable' do
88
+ specify do
89
+ expect { subject.enable_on_exec(:lease) }.to \
90
+ raise_error(
91
+ Cap2::IncompatibleCapabilities,
92
+ 'cannot enable_on_exec a capability which is neither permitted nor inheritable'
93
+ )
94
+ end
95
+ end
96
+
97
+ context 'when the capability is permitted' do
98
+ before(:each) do
99
+ run_as_root('permit(:lease)')
100
+ end
101
+
102
+ specify do
103
+ expect { running_as_root('enable_on_exec(:lease)') }.to \
104
+ change { subject.effective?(:lease) }.from(false).to(true)
105
+ end
106
+ end
107
+
108
+ context 'when the capability is inheritable' do
109
+ before(:each) do
110
+ run_as_root('allow_inherit(:lease)')
111
+ end
112
+
113
+ specify do
114
+ expect { running_as_root('enable_on_exec(:lease)') }.to \
115
+ change { subject.effective?(:lease) }.from(false).to(true)
116
+ end
117
+ end
118
+ end
119
+
120
+ describe '#disable_on_exec' do
121
+ before(:each) do
122
+ run_as_root('permit(:kill)', 'enable_on_exec(:kill)')
123
+ end
124
+
125
+ specify do
126
+ expect { running_as_root('disable_on_exec(:kill)') }.to \
127
+ change { subject.effective?(:kill) }.from(true).to(false)
128
+ end
129
+ end
130
+
131
+ # FIXME: Would like to call the given code on subject directly (e.g.
132
+ # `subject.permit(:fowner)`) but this would require the test
133
+ # suite to be run as root?
134
+ def run_as_root(*codes)
135
+ codes.each do |code|
136
+ system %{sudo ruby -Ilib -rcap2 -e 'Cap2.file("#{file.path}").#{code}'}
137
+ end
138
+ end
139
+ alias running_as_root run_as_root
140
+ end
@@ -1,5 +1,31 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Cap2::Process do
4
- it_behaves_like 'an entity'
4
+ describe '#permitted?' do
5
+ context "when the process doesn't have the given capability" do
6
+ subject { Cap2::Process.new(Process.pid) }
7
+
8
+ it { should_not be_permitted(:dac_override) }
9
+ end
10
+
11
+ context 'when the process does have the given capability' do
12
+ subject { Cap2::Process.new(1) }
13
+
14
+ it { should be_permitted(:dac_override) }
15
+ end
16
+ end
17
+
18
+ describe '#effective?' do
19
+ context "when the process doesn't have the given capability" do
20
+ subject { Cap2::Process.new(Process.pid) }
21
+
22
+ it { should_not be_effective(:dac_override) }
23
+ end
24
+
25
+ context 'when the process does have the given capability' do
26
+ subject { Cap2::Process.new(1) }
27
+
28
+ it { should be_effective(:dac_override) }
29
+ end
30
+ end
5
31
  end
@@ -7,7 +7,6 @@
7
7
 
8
8
  require 'cap2'
9
9
  require 'tempfile'
10
- require 'support/entity'
11
10
 
12
11
  RSpec.configure do |config|
13
12
  config.treat_symbols_as_metadata_keys_with_true_values = true
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cap2
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,12 +9,12 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-08-18 00:00:00.000000000 Z
12
+ date: 2012-08-30 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: ! " Cap2 is a Ruby library for managing the POSIX 1003.1e capabilities\n
15
- \ available in Linux kernels.\n \n These capabilities are a partitioning
16
- of the all powerful root\n privilege into a set of distinct privileges.\n \n
17
- \ See capabilites(7) for more information.\n"
15
+ \ available in Linux kernels.\n\n These capabilities are a partitioning of
16
+ the all powerful root\n privilege into a set of distinct privileges.\n\n See
17
+ capabilites(7) for more information.\n"
18
18
  email:
19
19
  - lewismarshall86@gmail.com
20
20
  executables: []
@@ -29,10 +29,10 @@ files:
29
29
  - ext/cap2/cap2.c
30
30
  - lib/cap2.rb
31
31
  - lib/cap2/process.rb
32
- - lib/cap2/entity.rb
33
32
  - lib/cap2/version.rb
34
33
  - lib/cap2/file.rb
35
- - spec/support/entity.rb
34
+ - lib/cap2/set_methods.rb
35
+ - lib/cap2.so
36
36
  - spec/process_spec.rb
37
37
  - spec/cap2_spec.rb
38
38
  - spec/spec_helper.rb
@@ -62,3 +62,4 @@ signing_key:
62
62
  specification_version: 3
63
63
  summary: A Ruby library for managing Linux file and process capabilities
64
64
  test_files: []
65
+ has_rdoc:
@@ -1,31 +0,0 @@
1
- module Cap2
2
- class Entity
3
- # = Entity
4
- #
5
- # A superclass for the Cap2::Process and Cap2::File
6
- # classes providing convenience methods for querying
7
- # permitted, effective and inheritable capabilities.
8
- #
9
- # Each method takes a capability argument, a lower
10
- # cased name of a capability, without the 'CAP_' prefix.
11
- # For example, :chown would query the CAP_CHOWN capability.
12
-
13
- def permitted?(capability)
14
- has?(:permitted, capability)
15
- end
16
-
17
- def effective?(capability)
18
- has?(:effective, capability)
19
- end
20
-
21
- def inheritable?(capability)
22
- has?(:inheritable, capability)
23
- end
24
-
25
- private
26
- def has?(set, cap)
27
- Cap2.has_capability?(@entity_id, set, cap)
28
- end
29
- end
30
- end
31
-
@@ -1,38 +0,0 @@
1
- require 'spec_helper'
2
-
3
- shared_examples_for 'an entity' do
4
- let(:entity_id) { double 'entity id' }
5
- let(:capability) { double 'capability' }
6
-
7
- subject { described_class.new(entity_id) }
8
-
9
- describe '#permitted?' do
10
- it 'should call Cap2.has_capability? correctly' do
11
- Cap2.
12
- should_receive(:has_capability?).
13
- with(entity_id, :permitted, capability)
14
-
15
- subject.permitted?(capability)
16
- end
17
- end
18
-
19
- describe '#effective?' do
20
- it 'should call Cap2.has_capability? correctly' do
21
- Cap2.
22
- should_receive(:has_capability?).
23
- with(entity_id, :effective, capability)
24
-
25
- subject.effective?(capability)
26
- end
27
- end
28
-
29
- describe '#inheritable?' do
30
- it 'should call Cap2.has_capability? correctly' do
31
- Cap2.
32
- should_receive(:has_capability?).
33
- with(entity_id, :inheritable, capability)
34
-
35
- subject.inheritable?(capability)
36
- end
37
- end
38
- end