101
101
# files/directories. This equates to passing the <tt>:noop</tt> and
102
102
# <tt>:verbose</tt> flags to methods in FileUtils.
103
103
#
104
+ # == Avoiding the TOCTTOU Vulnerability
105
+ #
106
+ # For certain methods that recursively remove entries,
107
+ # there is a potential vulnerability called the
108
+ # {Time-of-check to time-of-use}[https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use],
109
+ # or TOCTTOU, vulnerability that can exist when:
110
+ #
111
+ # - An ancestor directory of the entry at the target path is world writable;
112
+ # such directories include <tt>/tmp</tt>.
113
+ # - The directory tree at the target path includes:
114
+ #
115
+ # - A world-writable descendant directory.
116
+ # - A symbolic link.
117
+ #
118
+ # To avoid that vulnerability, you can use this method to remove entries:
119
+ #
120
+ # - FileUtils.remove_entry_secure: removes recursively
121
+ # if the target path points to a directory.
122
+ #
123
+ # Also available are these methods,
124
+ # each of which calls \FileUtils.remove_entry_secure:
125
+ #
126
+ # - FileUtils.rm_r with keyword argument <tt>secure: true</tt>.
127
+ # - FileUtils.rm_rf with keyword argument <tt>secure: true</tt>.
128
+ #
129
+ # Finally, this method for moving entries calls \FileUtils.remove_entry_secure
130
+ # if the source and destination are on different devices
131
+ # (which means that the "move" is really a copy and remove):
132
+ #
133
+ # - FileUtils.mv with keyword argument <tt>secure: true</tt>.
134
+ #
135
+ # \Method \FileUtils.remove_entry_secure removes securely
136
+ # by applying a special pre-process:
137
+ #
138
+ # - If the target path points to a directory, this method uses
139
+ # {chown(2)}[https://man7.org/linux/man-pages/man2/chown.2.html]
140
+ # and {chmod(2)}[https://man7.org/linux/man-pages/man2/chmod.2.html]
141
+ # in removing directories.
142
+ # - The owner of the target directory should be either the current process
143
+ # or the super user (root).
144
+ #
145
+ # WARNING: You must ensure that *ALL* parent directories cannot be
146
+ # moved by other untrusted users. For example, parent directories
147
+ # should not be owned by untrusted users, and should not be world
148
+ # writable except when the sticky bit is set.
149
+ #
150
+ # For details of this security vulnerability, see Perl cases:
151
+ #
152
+ # - {CVE-2005-0448}[https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448].
153
+ # - {CVE-2004-0452}[https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452].
154
+ #
104
155
module FileUtils
105
156
VERSION = "1.6.0"
106
157
@@ -197,7 +248,7 @@ def remove_trailing_slash(dir) #:nodoc:
197
248
#
198
249
# Creates directories at the paths in the given +list+
199
250
# (an array of strings or a single string);
200
- # returns +list+.
251
+ # returns +list+ if it is an array, <tt>[list]</tt> otherwise .
201
252
#
202
253
# With no keyword arguments, creates a directory at each +path+ in +list+
203
254
# by calling: <tt>Dir.mkdir(path, mode)</tt>;
@@ -239,7 +290,7 @@ def mkdir(list, mode: nil, noop: nil, verbose: nil)
239
290
# Creates directories at the paths in the given +list+
240
291
# (an array of strings or a single string),
241
292
# also creating ancestor directories as needed;
242
- # returns +list+.
293
+ # returns +list+ if it is an array, <tt>[list]</tt> otherwise .
243
294
#
244
295
# With no keyword arguments, creates a directory at each +path+ in +list+,
245
296
# along with any needed ancestor directories,
@@ -311,7 +362,7 @@ def fu_mkdir(path, mode) #:nodoc:
311
362
#
312
363
# Removes directories at the paths in the given +list+
313
364
# (an array of strings or a single string);
314
- # returns +list+.
365
+ # returns +list+, if it is an array, <tt>[list]</tt> otherwise .
315
366
#
316
367
# With no keyword arguments, removes the directory at each +path+ in +list+,
317
368
# by calling: <tt>Dir.rmdir(path)</tt>;
@@ -865,6 +916,10 @@ def copy_stream(src, dest)
865
916
# If +src+ and +dest+ are on different devices,
866
917
# first copies, then removes +src+.
867
918
#
919
+ # May cause a local vulnerability if not called with keyword argument
920
+ # <tt>secure: true</tt>;
921
+ # see {Avoiding the TOCTTOU Vulnerability}[rdoc-ref:FileUtils@Avoiding+the+TOCTTOU+Vulnerability].
922
+ #
868
923
# If +src+ is the path to a single file or directory and +dest+ does not exist,
869
924
# moves +src+ to +dest+:
870
925
#
@@ -898,13 +953,14 @@ def copy_stream(src, dest)
898
953
# | `-- src.txt
899
954
# `-- src1.txt
900
955
#
901
- # - <tt>force: true</tt> - attempts to force the move;
902
- # if the move includes removing +src+
956
+ # Keyword arguments:
957
+ #
958
+ # - <tt>force: true</tt> - if the move includes removing +src+
903
959
# (that is, if +src+ and +dest+ are on different devices),
904
960
# ignores raised exceptions of StandardError and its descendants.
905
961
# - <tt>noop: true</tt> - does not move files.
906
- # - <tt>secure: true</tt> - removes +src+ securely
907
- # by calling FileUtils.remove_entry_secure.
962
+ # - <tt>secure: true</tt> - removes +src+ securely;
963
+ # see details at FileUtils.remove_entry_secure.
908
964
# - <tt>verbose: true</tt> - prints an equivalent command:
909
965
#
910
966
# FileUtils.mv('src0', 'dest0', noop: true, verbose: true)
@@ -949,13 +1005,29 @@ def mv(src, dest, force: nil, noop: nil, verbose: nil, secure: nil)
949
1005
alias move mv
950
1006
module_function :move
951
1007
1008
+ # Removes entries at the paths in the given +list+
1009
+ # (an array of strings or a single string);
1010
+ # returns +list+, if it is an array, <tt>[list]</tt> otherwise.
1011
+ #
1012
+ # With no keyword arguments, removes files at the paths given in +list+:
1013
+ #
1014
+ # FileUtils.touch(['src0.txt', 'src0.dat'])
1015
+ # FileUtils.rm(['src0.dat', 'src0.txt']) # => ["src0.dat", "src0.txt"]
1016
+ #
1017
+ # Keyword arguments:
1018
+ #
1019
+ # - <tt>force: true</tt> - ignores raised exceptions of StandardError
1020
+ # and its descendants.
1021
+ # - <tt>noop: true</tt> - does not remove files; returns +nil+.
1022
+ # - <tt>verbose: true</tt> - prints an equivalent command:
1023
+ #
1024
+ # FileUtils.rm(['src0.dat', 'src0.txt'], noop: true, verbose: true)
1025
+ #
1026
+ # Output:
952
1027
#
953
- # Remove file(s) specified in +list+. This method cannot remove directories.
954
- # All StandardErrors are ignored when the :force option is set.
1028
+ # rm src0.dat src0.txt
955
1029
#
956
- # FileUtils.rm %w( junk.txt dust.txt )
957
- # FileUtils.rm Dir.glob('*.so')
958
- # FileUtils.rm 'NotExistFile', force: true # never raises exception
1030
+ # FileUtils.remove is an alias for FileUtils.rm.
959
1031
#
960
1032
def rm ( list , force : nil , noop : nil , verbose : nil )
961
1033
list = fu_list ( list )
@@ -971,10 +1043,13 @@ def rm(list, force: nil, noop: nil, verbose: nil)
971
1043
alias remove rm
972
1044
module_function :remove
973
1045
1046
+ # Equivalent to:
974
1047
#
975
- # Equivalent to
1048
+ # FileUtils.rm(list, force: true, **kwargs)
976
1049
#
977
- # FileUtils.rm(list, force: true)
1050
+ # See FileUtils.rm for keyword arguments.
1051
+ #
1052
+ # FileUtils.safe_unlink is an alias for FileUtils.rm_f.
978
1053
#
979
1054
def rm_f ( list , noop : nil , verbose : nil )
980
1055
rm list , force : true , noop : noop , verbose : verbose
@@ -984,24 +1059,50 @@ def rm_f(list, noop: nil, verbose: nil)
984
1059
alias safe_unlink rm_f
985
1060
module_function :safe_unlink
986
1061
1062
+ # Removes entries at the paths in the given +list+
1063
+ # (an array of strings or a single string);
1064
+ # returns +list+, if it is an array, <tt>[list]</tt> otherwise.
987
1065
#
988
- # remove files +list+[0] +list+[1]... If +list+[n] is a directory,
989
- # removes its all contents recursively. This method ignores
990
- # StandardError when :force option is set .
1066
+ # May cause a local vulnerability if not called with keyword argument
1067
+ # <tt>secure: true</tt>;
1068
+ # see {Avoiding the TOCTTOU Vulnerability}[rdoc-ref:FileUtils@Avoiding+the+TOCTTOU+Vulnerability] .
991
1069
#
992
- # FileUtils.rm_r Dir.glob('/tmp/*')
993
- # FileUtils.rm_r 'some_dir', force: true
1070
+ # For each file path, removes the file at that path:
994
1071
#
995
- # WARNING: This method causes local vulnerability
996
- # if one of parent directories or removing directory tree are world
997
- # writable (including /tmp, whose permission is 1777), and the current
998
- # process has strong privilege such as Unix super user (root), and the
999
- # system has symbolic link. For secure removing, read the documentation
1000
- # of remove_entry_secure carefully, and set :secure option to true.
1001
- # Default is <tt>secure: false</tt>.
1072
+ # FileUtils.touch(['src0.txt', 'src0.dat'])
1073
+ # FileUtils.rm_r(['src0.dat', 'src0.txt'])
1074
+ # File.exist?('src0.txt') # => false
1075
+ # File.exist?('src0.dat') # => false
1002
1076
#
1003
- # NOTE: This method calls remove_entry_secure if :secure option is set.
1004
- # See also remove_entry_secure.
1077
+ # For each directory path, recursively removes files and directories:
1078
+ #
1079
+ # system('tree --charset=ascii src1')
1080
+ # src1
1081
+ # |-- dir0
1082
+ # | |-- src0.txt
1083
+ # | `-- src1.txt
1084
+ # `-- dir1
1085
+ # |-- src2.txt
1086
+ # `-- src3.txt
1087
+ # FileUtils.rm_r('src1')
1088
+ # File.exist?('src1') # => false
1089
+ #
1090
+ # Keyword arguments:
1091
+ #
1092
+ # - <tt>force: true</tt> - ignores raised exceptions of StandardError
1093
+ # and its descendants.
1094
+ # - <tt>noop: true</tt> - does not remove entries; returns +nil+.
1095
+ # - <tt>secure: true</tt> - removes +src+ securely;
1096
+ # see details at FileUtils.remove_entry_secure.
1097
+ # - <tt>verbose: true</tt> - prints an equivalent command:
1098
+ #
1099
+ # FileUtils.rm_r(['src0.dat', 'src0.txt'], noop: true, verbose: true)
1100
+ # FileUtils.rm_r('src1', noop: true, verbose: true)
1101
+ #
1102
+ # Output:
1103
+ #
1104
+ # rm -r src0.dat src0.txt
1105
+ # rm -r src1
1005
1106
#
1006
1107
def rm_r ( list , force : nil , noop : nil , verbose : nil , secure : nil )
1007
1108
list = fu_list ( list )
@@ -1017,13 +1118,17 @@ def rm_r(list, force: nil, noop: nil, verbose: nil, secure: nil)
1017
1118
end
1018
1119
module_function :rm_r
1019
1120
1121
+ # Equivalent to:
1122
+ #
1123
+ # FileUtils.rm_r(list, force: true, **kwargs)
1020
1124
#
1021
- # Equivalent to
1125
+ # May cause a local vulnerability if not called with keyword argument
1126
+ # <tt>secure: true</tt>;
1127
+ # see {Avoiding the TOCTTOU Vulnerability}[rdoc-ref:FileUtils@Avoiding+the+TOCTTOU+Vulnerability].
1022
1128
#
1023
- # FileUtils.rm_r(list, force: true)
1129
+ # See FileUtils.rm_r for keyword arguments.
1024
1130
#
1025
- # WARNING: This method causes local vulnerability.
1026
- # Read the documentation of rm_r first.
1131
+ # FileUtils.rmtree is an alias for FileUtils.rm_rf.
1027
1132
#
1028
1133
def rm_rf ( list , noop : nil , verbose : nil , secure : nil )
1029
1134
rm_r list , force : true , noop : noop , verbose : verbose , secure : secure
@@ -1033,37 +1138,12 @@ def rm_rf(list, noop: nil, verbose: nil, secure: nil)
1033
1138
alias rmtree rm_rf
1034
1139
module_function :rmtree
1035
1140
1141
+ # Securely removes the entry given by +path+,
1142
+ # which should be the entry for a regular file, a symbolic link,
1143
+ # or a directory.
1036
1144
#
1037
- # This method removes a file system entry +path+. +path+ shall be a
1038
- # regular file, a directory, or something. If +path+ is a directory,
1039
- # remove it recursively. This method is required to avoid TOCTTOU
1040
- # (time-of-check-to-time-of-use) local security vulnerability of rm_r.
1041
- # #rm_r causes security hole when:
1042
- #
1043
- # * Parent directory is world writable (including /tmp).
1044
- # * Removing directory tree includes world writable directory.
1045
- # * The system has symbolic link.
1046
- #
1047
- # To avoid this security hole, this method applies special preprocess.
1048
- # If +path+ is a directory, this method chown(2) and chmod(2) all
1049
- # removing directories. This requires the current process is the
1050
- # owner of the removing whole directory tree, or is the super user (root).
1051
- #
1052
- # WARNING: You must ensure that *ALL* parent directories cannot be
1053
- # moved by other untrusted users. For example, parent directories
1054
- # should not be owned by untrusted users, and should not be world
1055
- # writable except when the sticky bit set.
1056
- #
1057
- # WARNING: Only the owner of the removing directory tree, or Unix super
1058
- # user (root) should invoke this method. Otherwise this method does not
1059
- # work.
1060
- #
1061
- # For details of this security vulnerability, see Perl's case:
1062
- #
1063
- # * https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448
1064
- # * https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452
1065
- #
1066
- # For fileutils.rb, this vulnerability is reported in [ruby-dev:26100].
1145
+ # Avoids a local vulnerability that can exist in certain circumstances;
1146
+ # see {Avoiding the TOCTTOU Vulnerability}[rdoc-ref:FileUtils@Avoiding+the+TOCTTOU+Vulnerability].
1067
1147
#
1068
1148
def remove_entry_secure ( path , force = false )
1069
1149
unless fu_have_symlink?
0 commit comments