homefs 0.3.0 → 0.3.1

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.
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