homefs 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/lib/homefs/debug.rb +4 -0
  3. data/lib/homefs/homefs.rb +110 -255
  4. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f11d42ee83c4b4ebf69e367d4ae58cc1a4927fac
4
- data.tar.gz: c8bba0a2cad6c689a3865c910c7d172c14bb9933
3
+ metadata.gz: 9aa5fb16b6856552286724fd6fd1e45f46bc5a6f
4
+ data.tar.gz: ca26c140a4ad789d383edbe43a7040d2fa23b8f4
5
5
  SHA512:
6
- metadata.gz: d31980d7d5be1f62af06b45e14ae95bfc1001202eb674edb479921730c5fea3eb7c5908158b613fe7ff721edf418554cf1d29a8a7678d269c76ed6e30ab08156
7
- data.tar.gz: 714090d6643662fe47fdc65987fd867a8da0368ef5cfa9534211ea314120ec5eadd53914ece6196e7d8e58f17c004bd13dbfb2426746fd1e5e98685a428426ae
6
+ metadata.gz: 7294eef3cd9569a078789e8b148de97054584084e792deb5bee24d5c5d234314fc7ea6d0d7dc11bc69d93002ec1e34cf9e455edc54b5af75e9428e1517e87349
7
+ data.tar.gz: 5b350ef36937e79201cf61c005acb21447ad014f51c96a1dd06f52753402e19d189e083f339fec10e1536e038bc41a257452878bc52f494ba6bbb1166ab75ddd
data/lib/homefs/debug.rb CHANGED
@@ -10,9 +10,13 @@ class HomeFS
10
10
  ret = object.send(method, *args)
11
11
  warn "\tMethod returned #{ret.inspect}"
12
12
  return ret
13
+ rescue SystemCallError => e
14
+ warn e
15
+ raise e
13
16
  rescue Exception => e
14
17
  warn e
15
18
  warn e.backtrace
19
+ raise e
16
20
  end
17
21
  end
18
22
  end
data/lib/homefs/homefs.rb CHANGED
@@ -22,7 +22,6 @@ class HomeFS
22
22
  def initialize(path, options = Hash.new)
23
23
  @relpath = path
24
24
  read_passwd
25
- read_group
26
25
  end
27
26
 
28
27
  # Read or re-read /etc/passwd to update the internal table of home
@@ -55,30 +54,6 @@ class HomeFS
55
54
  }
56
55
  end
57
56
 
58
- # Read or re-read /etc/group to update the internal table of
59
- # group membership.
60
- # @return [void]
61
- def read_group
62
- # Try to read from getent
63
- group = `getent group`
64
- if $? != 0
65
- group = File.read('/etc/group')
66
- end
67
-
68
- @groups = Hash.new
69
-
70
- group.each_line.map { |line|
71
- next if line.strip.empty?
72
- _name, _, gid, members = line.split(':')
73
- members = members.strip.split(',').map {|member| @usernames[member]}
74
- [Integer(gid), members]
75
- }.reject { |line|
76
- line.nil?
77
- }.each { |gid, members|
78
- @groups[gid] = members
79
- }
80
- end
81
-
82
57
  # Return the path to the root of HomeFS relative to the underlying
83
58
  # filesystem. If _path_ is specified, it is taken to be relative to the
84
59
  # root of HomeFS.
@@ -99,162 +74,28 @@ class HomeFS
99
74
  end
100
75
  end
101
76
 
102
- # Check whether the given file has a mode that matches the given mask. If
103
- # check_ids is true, the third-least significant digit of the mask is set to
104
- # zero if the calling user is not the owner of the file, and the
105
- # second-least significant digit of the mask is zeroed if the calling user's
106
- # group is not the group of the file. This has the effect of only checking
107
- # the owner's permissions if we're the owner, and only checking the group
108
- # permissions if we're in that group.
109
- #
110
- # This method checks if _any_ of the set bits in the mask are set in the
111
- # file's mode. It does not check if the mask matches the file's mode, nor
112
- # does it check if _all_ set bits in the mask are set in the file's mode.
113
- #
114
- # The first three least significant digits of the mode, in octal, (that is,
115
- # the three rightmost digits) control the read (4), write (2), and execute
116
- # (1) permissions of the file. The next digit controls setuid (4), setgid
117
- # (2), and the sticky bit (1) of the file.
118
- #
119
- # It is recommended to see
120
- # man chmod
121
- # to get a more detailed description about file modes.
122
- #
123
- # @example Test if we can write to a file
124
- # mode_mask("/example", 0222, true)
125
- # mode_mask("/example", 0222)
126
- # @example Test if a file is executable by anyone
127
- # mode_mask("/binary", 0111, false)
128
- # @example Test if a directory has the sticky bit set
129
- # mode_mask("/tmp", 01000, false)
130
- #
131
- # @param [String] file the path to the file to check
132
- # @param [Integer] mask the mask against which to check the file's mode.
133
- # It is recommended you write this in octal (with a leading 0).
134
- # @param [Integer] uid if not nil, only check user and group permissions
135
- # if the user it represents is, respectively, the owner of the
136
- # file/in the file's group.
137
- # @return [Boolean] whether the mode of _file_ matches the given mask
138
- def mode_mask(file, mask, uid = nil)
139
- stat = File.stat(file)
140
- fmode = stat.mode
141
- if uid
142
- fuid, fgid = stat.uid, stat.gid
143
- # Zero out the third digit (in octal).
144
- # We could use a constant here, but this works
145
- # for a mask of any length
146
- if uid != fuid
147
- mask &= ~(mask & 0700)
148
- end
149
- if !@groups[fgid].include?(uid)
150
- mask &= ~(mask & 0070)
77
+ # If HomeFS is running as root, drop_priv sets the effective user ID
78
+ # and effective group ID to _uid_ and _gid_, respectively, and then
79
+ # calls the block passed to the method. Before returning, drop_priv
80
+ # that the EUID and EGID are each set back to 0.
81
+ # @param [Integer] uid the user ID to set the effective user ID to
82
+ # @param [Integer] gid the group ID to set the effective group ID to
83
+ # @yield an environment with the EUID and EGID set to _uid_ and _gid_
84
+ # @return the result of the block
85
+ def drop_priv(uid, gid)
86
+ if Process::Sys.getuid == 0
87
+ begin
88
+ Process::Sys.setegid(gid)
89
+ Process::Sys.seteuid(uid)
90
+ ret = yield
91
+ ensure
92
+ Process::Sys.seteuid(0)
93
+ Process::Sys.setegid(0)
151
94
  end
152
- end
153
- fmode & mask != 0
154
- end
155
-
156
- # Test whether we can write to the given path. If no file exists at _path_,
157
- # we test if the parent directory is either writable or has the sticky bit
158
- # set.
159
- # @param [String] path the path to test
160
- # @param [Integer] uid the UID to check the privilage of. If nil, this
161
- # method returns whether the file is writable by anyone.
162
- # @return [Boolean] whether the given path is writable
163
- def writable?(path, uid = nil)
164
- if !File.exist?(path)
165
- mode_mask(File.dirname(path), 01222, uid)
166
95
  else
167
- mode_mask(path, 0222, uid)
168
- end
169
- end
170
-
171
- # Test whether the user described by _uid_ can read from the given
172
- # path. If no file exists at the given path, an Errno::ENOENT is
173
- # raised.
174
- # @param [String] path the path to test
175
- # @param [Integer] uid the UID to check the privilage of. If nil, this
176
- # method returns whether the file is readable by anyone.
177
- # @return [Boolean] whether the given path is readable
178
- # @raise [Errno::ENOENT] if no file exists at _path_
179
- def readable?(path, uid = nil)
180
- mode_mask(path, 0444, uid)
181
- end
182
-
183
- # Test whether the user described by _uid_ can execute the given
184
- # path. If no file exists at the given path, an Errno::ENOENT is
185
- # raised.
186
- # @param [String] path the path to test
187
- # @param [Integer] uid the UID to check the privilage of. If nil, this
188
- # method returns whether the file is executable by anyone.
189
- # @return [Boolean] whether the given path is executable
190
- # @raise [Errno::ENOENT] if no file exists at _path_
191
- def executable?(path, uid = nil)
192
- mode_mask(path, 0111, uid)
193
- end
194
-
195
- # This method raises an Errno::EACCES if the user given by _uid_ cannot
196
- # write to the given path. The check is the same as in {#writable?}.
197
- # @param [String] relpath the path to test, relative to the root of
198
- # HomeFS (not the actual filesystem)
199
- # @param [Integer] uid the UID to check the privilage of
200
- # @return [void]
201
- # @raise [Errno::EACCES] if the path is not writable by the given user
202
- def check_writable(uid, relpath)
203
- hpath = homepath(uid, relpath)
204
- unless writable?(hpath, uid)
205
- fail Errno::EACCES, relpath
206
- end
207
- end
208
-
209
- # This method raises an Errno::EACCES if the user given by _uid_ cannot
210
- # read from the given path.
211
- # @param [String] relpath the path to test, relative to the root of
212
- # HomeFS (not the actual filesystem)
213
- # @param [Integer] uid the UID to check the privilage of
214
- # @return [void]
215
- # @raise [Errno::EACCES] if the path is not readable by the given user
216
- # @raise [Errno::ENOENT] if no file or directory exists at the given
217
- # path
218
- def check_readable(uid, relpath)
219
- hpath = homepath(uid, relpath)
220
- unless readable?(hpath, uid)
221
- fail Errno::EACCES, relpath
222
- end
223
- end
224
-
225
- # This method raises an Errno::EACCES if the user given by _uid_ cannot
226
- # list the given directory, i.e., if the user does not have the execute
227
- # and read permissions on the given directory. If the path given is not
228
- # a directory, the dirname of the given path is tested.
229
- # @param [String] relpath the path to test, relative to the root of
230
- # HomeFS (not the actual filesystem)
231
- # @param [Integer] uid the UID to check the privilage of
232
- # @return [void]
233
- # @raise [Errno::EACCES] if the path is not readable by the given user
234
- def check_listable(uid, relpath)
235
- hpath = homepath(uid, relpath)
236
- unless File.directory?(hpath)
237
- hpath = File.dirname(hpath)
238
- end
239
- unless readable?(hpath) && executable?(hpath)
240
- fail Errno::EACCES, relpath
241
- end
242
- end
243
-
244
- # This method raises an Errno::EACCES if the user given by _uid_ is not
245
- # the owner of the file described by _relpath_
246
- # @param [String] relpath the path to test, relative to the root of
247
- # HomeFS (not the actual filesystem)
248
- # @param [Integer] uid the UID to check ownership of
249
- # @return [void]
250
- # @raise [Errno::EACCES] if the path is not owned by the given user
251
- # @raise [Errno::ENOENT] if no file or directory exists at the given
252
- # path
253
- def check_owner(uid, relpath)
254
- hpath = File.dirname(homepath(uid, relpath))
255
- unless File.stat(hpath).uid == uid
256
- fail Errno::EACCES, relpath
96
+ ret = yield
257
97
  end
98
+ return ret
258
99
  end
259
100
 
260
101
  # Check access permissions
@@ -269,8 +110,16 @@ class HomeFS
269
110
  # called directly
270
111
  def access(context, path, mode)
271
112
  uid = context.uid
272
- unless mode_mask(homepath(uid, path), mode * 0111, uid)
273
- raise Errno::EACCES
113
+ hpath = homepath(uid, path)
114
+ drop_priv(uid, context.gid) do
115
+ access = true
116
+ check_read = (mode & 4 != 0)
117
+ check_write = (mode & 2 != 0)
118
+ check_execute = (mode & 1 != 0)
119
+ access &&= File.readable?(hpath) if check_read
120
+ access &&= File.writable?(hpath) if check_write
121
+ access &&= File.executable?(hpath) if check_execute
122
+ access
274
123
  end
275
124
  end
276
125
 
@@ -285,9 +134,11 @@ class HomeFS
285
134
  # @note This method should usually only be called by FUSE, and not
286
135
  # called directly
287
136
  def chmod(context, path, mode)
288
- hpath = homepath(context.uid, path)
289
- check_owner(context.uid, path)
290
- File.chmod(mode, hpath)
137
+ uid = context.uid
138
+ hpath = homepath(uid, path)
139
+ drop_priv(uid, context.gid) do
140
+ File.chmod(mode, hpath)
141
+ end
291
142
  end
292
143
 
293
144
  # Change file ownership
@@ -302,9 +153,11 @@ class HomeFS
302
153
  # @note This method should usually only be called by FUSE, and not
303
154
  # called directly
304
155
  def chown(context, path, uid, gid)
305
- raise Errno::EACCES, path if context.uid != 0
306
- hpath = homepath(context.uid, path)
307
- File.chown(uid, gid, hpath)
156
+ uid = context.uid
157
+ hpath = homepath(uid, path)
158
+ drop_priv(uid, context.gid) do
159
+ File.chown(mode, hpath)
160
+ end
308
161
  end
309
162
 
310
163
  # Create and open a file
@@ -321,23 +174,11 @@ class HomeFS
321
174
  # @note This method should usually only be called by FUSE, and not
322
175
  # called directly
323
176
  def create(context, path, mode, ffi)
324
- check_writable(context.uid, path)
325
- hpath = homepath(context.uid, path)
326
-
327
- # It is important that we create the file and chown it before we
328
- # set its mode to prevent a possible privilage escalation
329
- # race-condition. To be more specific, if this filesystem is
330
- # running as root (which it usually is), the file will be created
331
- # owned by root. If the mode were set when the file is created,
332
- # then an attacker could create a setuid file writable by anyone,
333
- # and then, in the 'real' filesystem (not HomeFS), quickly write a
334
- # small program that just executes /bin/bash. If the write
335
- # completed before handle.chown was called, the attacker could have
336
- # a setuid shell.
337
- handle = File.new(hpath, File::CREAT | File::WRONLY, 0600)
338
- handle.chown(context.uid, context.gid)
339
- handle.chmod(mode)
340
- ffi.fh = handle
177
+ uid = context.uid
178
+ hpath = homepath(uid, path)
179
+ ffi.fh = drop_priv(uid, context.gid) do
180
+ File.new(hpath, File::CREAT | File::WRONLY, mode)
181
+ end
341
182
  end
342
183
 
343
184
  # Get attributes of an open file
@@ -431,8 +272,11 @@ class HomeFS
431
272
  # @note This method should usually only be called by FUSE, and not
432
273
  # called directly
433
274
  def getattr(context, path)
434
- check_listable(context.uid, path)
435
- File.lstat(homepath(context.uid, path))
275
+ uid = context.uid
276
+ hpath = homepath(uid, path)
277
+ drop_priv(uid, context.gid) do
278
+ File.lstat(hpath)
279
+ end
436
280
  end
437
281
 
438
282
  # Called when filesystem is initialised
@@ -457,20 +301,22 @@ class HomeFS
457
301
  # @note This method should usually only be called by FUSE, and not
458
302
  # called directly
459
303
  def link(context, from, to)
460
- hfrom = homepath(context.uid, from)
461
- check_writable(context.uid, from)
462
- File.link(hfrom, to)
463
- File.chown(context.uid, hfrom) if context.uid == 0
304
+ uid = context.uid
305
+ hfrom = homepath(uid, from)
306
+ drop_priv(uid, context.gid) do
307
+ File.link(hfrom, to)
308
+ end
464
309
  end
465
310
 
466
311
  # (see RFuse::Fuse#mkdir)
467
312
  # @note This method should usually only be called by FUSE, and not
468
313
  # called directly
469
314
  def mkdir(context, path, mode)
470
- check_writable(context.uid, path)
471
- hpath = homepath(context.uid, path)
472
- Dir.mkdir(hpath, mode)
473
- File.chown(context.uid, context.gid, hpath) if context.uid == 0
315
+ uid = context.uid
316
+ hpath = homepath(uid, path)
317
+ drop_priv(uid, context.gid) do
318
+ Dir.mkdir(hpath, mode)
319
+ end
474
320
  end
475
321
 
476
322
  # File open operation
@@ -488,24 +334,17 @@ class HomeFS
488
334
  # @note This method should usually only be called by FUSE, and not
489
335
  # called directly
490
336
  def open(context, path, ffi)
491
- if ffi.flags & File::RDWR != 0
492
- mask = 0666
493
- elsif ffi.flags & File::WRONLY != 0
494
- mask = 0222
495
- else
496
- mask = 0444
497
- end
498
- hpath = homepath(context.uid, path)
499
- unless mode_mask(hpath, mask, context.uid)
500
- fail Errno::EACCES, path
501
- end
337
+ uid = context.uid
338
+ hpath = homepath(uid, path)
502
339
 
503
340
  # We pass the flags straight into File.open because the constants
504
341
  # in RFuse::Fcntl and File::Constants have the same values, because
505
342
  # they both have the same values as the open syscall. If this were
506
343
  # not the case, we'd have the map the values of RFuse::Fcntl to
507
344
  # their equivalents in File::Constants
508
- ffi.fh = File.open(hpath, ffi.flags)
345
+ drop_priv(uid, context.gid) do
346
+ ffi.fh = File.open(hpath, ffi.flags)
347
+ end
509
348
  end
510
349
 
511
350
  # Open directory
@@ -524,8 +363,11 @@ class HomeFS
524
363
  # @note This method should usually only be called by FUSE, and not
525
364
  # called directly
526
365
  def opendir(context, path, ffi)
527
- check_listable(context.uid, path)
528
- ffi.fh = Dir.new(homepath(context.uid, path))
366
+ uid = context.uid
367
+ hpath = homepath(uid, path)
368
+ ffi.fh = drop_priv(uid, context.gid) do
369
+ Dir.new(hpath)
370
+ end
529
371
  end
530
372
 
531
373
  # Read data from an open file
@@ -547,7 +389,7 @@ class HomeFS
547
389
  else
548
390
  ffi.fh.seek(offset, :SET)
549
391
  end
550
- ffi.fh.read(size)
392
+ ffi.fh.read(size) || ''
551
393
  end
552
394
 
553
395
  # (see RFuse::Fuse#readdir)
@@ -603,12 +445,12 @@ class HomeFS
603
445
  # @note This method should usually only be called by FUSE, and not
604
446
  # called directly
605
447
  def rename(context, from, to)
606
- hfrom = homepath(context.uid, from)
607
- hto = homepath(context.uid, to)
608
- check_readable(context.uid, from)
609
- check_writable(File.dirname(from), context.uid)
610
- check_writable(File.dirname(to), context.uid)
611
- FileUtils.mv(hfrom, hto, :force => true)
448
+ uid = context.uid
449
+ hfrom = homepath(uid, from)
450
+ hto = homepath(uid, to)
451
+ drop_priv(uid, context.gid) do
452
+ FileUtils.mv(hfrom, hto, :force => true)
453
+ end
612
454
  end
613
455
 
614
456
  # Create a symbolic link
@@ -624,12 +466,10 @@ class HomeFS
624
466
  # @note This method should usually only be called by FUSE, and not
625
467
  # called directly
626
468
  def symlink(context, to, from)
627
- check_writable(context.uid, from)
628
- hfrom = homepath(context.uid, from)
629
- File.symlink(to, hfrom)
630
- begin
631
- File.lchown(context.uid, context.gid, hfrom)
632
- rescue
469
+ uid = context.uid
470
+ hfrom = homepath(uid, from)
471
+ drop_priv(uid, context.gid) do
472
+ File.symlink(to, hfrom)
633
473
  end
634
474
  end
635
475
 
@@ -644,8 +484,11 @@ class HomeFS
644
484
  # @note This method should usually only be called by FUSE, and not
645
485
  # called directly
646
486
  def truncate(context, path, offset)
647
- check_writable(context.uid, path)
648
- File.truncate(homepath(context.uid, path), offset)
487
+ uid = context.uid
488
+ hpath = homepath(uid, path)
489
+ drop_priv(uid, context.gid) do
490
+ File.truncate(hpath, offset)
491
+ end
649
492
  end
650
493
 
651
494
  # Remove a file
@@ -658,8 +501,11 @@ class HomeFS
658
501
  # @note This method should usually only be called by FUSE, and not
659
502
  # called directly
660
503
  def unlink(context, path)
661
- check_writable(context.uid, File.dirname(path))
662
- File.unlink(homepath(context.uid, path))
504
+ uid = context.uid
505
+ hpath = homepath(uid, path)
506
+ drop_priv(uid, context.gid) do
507
+ File.unlink(hpath)
508
+ end
663
509
  end
664
510
 
665
511
  # Change access/modification times of a file
@@ -674,8 +520,11 @@ class HomeFS
674
520
  # @note This method should usually only be called by FUSE, and not
675
521
  # called directly
676
522
  def utimens(context, path, actime, modtime)
677
- check_writable(context.uid, path)
678
- File.utime(actime, modtime, homepath(context.uid, path))
523
+ uid = context.uid
524
+ hpath = homepath(uid, path)
525
+ drop_priv(uid, context.gid) do
526
+ File.utime(actime, modtime, hpath)
527
+ end
679
528
  end
680
529
 
681
530
  # Write data to an open file
@@ -717,9 +566,11 @@ begin
717
566
  # @note This method is only defined if the gem 'ffi-xattr' is
718
567
  # available
719
568
  def getxattr(context, path, name)
720
- check_readable(context.uid, path)
721
- hpath = homepath(context.uid, path)
722
- check_nodata Xattr::Lib.get(hpath, false, name)
569
+ uid = context.uid
570
+ hpath = homepath(uid, path)
571
+ drop_priv(uid, context.gid) do
572
+ check_nodata Xattr::Lib.get(hpath, false, name)
573
+ end
723
574
  end
724
575
 
725
576
  # Set extended attributes
@@ -737,9 +588,11 @@ begin
737
588
  # @note This method is only defined if the gem 'ffi-xattr' is
738
589
  # available
739
590
  def setxattr(context, path, name, data, flags)
740
- check_writable(context.uid, path)
741
- hpath = homepath(context.uid, path)
742
- Xattr::Lib.set(hpath, false, name, data)
591
+ uid = context.uid
592
+ hpath = homepath(uid, path)
593
+ drop_priv(uid, context.gid) do
594
+ Xattr::Lib.set(hpath, false, name, data)
595
+ end
743
596
  end
744
597
 
745
598
  # List extended attributes
@@ -754,9 +607,11 @@ begin
754
607
  # @note This method is only defined if the gem 'ffi-xattr' is
755
608
  # available
756
609
  def listxattr(context, path)
757
- check_readable(context.uid, path)
758
- hpath = homepath(context.uid, path)
759
- check_nodata Xattr::Lib.list(hpath, false)
610
+ uid = context.uid
611
+ hpath = homepath(uid, path)
612
+ drop_priv(uid, context.gid) do
613
+ check_nodata Xattr::Lib.list(hpath, false)
614
+ end
760
615
  end
761
616
 
762
617
  # Remove extended attribute
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: homefs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dylan Frese
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-10-28 00:00:00.000000000 Z
11
+ date: 2015-10-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rfuse