diff --git a/CHANGELOG.md b/CHANGELOG.md index 00b93dcda611..ed8752231263 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Bug fixes: * Fix `StringIO` to set position correctly after reading multi-byte characters (#2207, @aardvark179). * Update `Process` methods to use `module_function` (@bjfish). +* Fix `File::Stat`'s `#executable?` and `#executable_real?` predicates that unconditionally returned `true` for a superuser (#2690, @andrykonchin). Compatibility: diff --git a/spec/mspec/lib/mspec/guards/superuser.rb b/spec/mspec/lib/mspec/guards/superuser.rb index e92ea7e86289..24daf9b26cd2 100644 --- a/spec/mspec/lib/mspec/guards/superuser.rb +++ b/spec/mspec/lib/mspec/guards/superuser.rb @@ -6,10 +6,20 @@ def match? end end +class RealSuperUserGuard < SpecGuard + def match? + Process.uid == 0 + end +end + def as_superuser(&block) SuperUserGuard.new.run_if(:as_superuser, &block) end +def as_real_superuser(&block) + RealSuperUserGuard.new.run_if(:as_real_superuser, &block) +end + def as_user(&block) SuperUserGuard.new.run_unless(:as_user, &block) end diff --git a/spec/ruby/shared/file/executable.rb b/spec/ruby/shared/file/executable.rb index 7b5c4c580c2b..baa156de9862 100644 --- a/spec/ruby/shared/file/executable.rb +++ b/spec/ruby/shared/file/executable.rb @@ -39,6 +39,41 @@ -> { @object.send(@method, nil) }.should raise_error(TypeError) -> { @object.send(@method, false) }.should raise_error(TypeError) end + + platform_is_not :windows do + as_superuser do + context "when run by a superuser" do + before :each do + @file = tmp('temp3.txt') + touch @file + end + + after :each do + rm_r @file + end + + it "returns true if file owner has permission to execute" do + File.chmod(0766, @file) + @object.send(@method, @file).should == true + end + + it "returns true if group has permission to execute" do + File.chmod(0676, @file) + @object.send(@method, @file).should == true + end + + it "returns true if other have permission to execute" do + File.chmod(0667, @file) + @object.send(@method, @file).should == true + end + + it "return false if nobody has permission to execute" do + File.chmod(0666, @file) + @object.send(@method, @file).should == false + end + end + end + end end describe :file_executable_missing, shared: true do diff --git a/spec/ruby/shared/file/executable_real.rb b/spec/ruby/shared/file/executable_real.rb index ce3d5ca1768d..bf2734ea071f 100644 --- a/spec/ruby/shared/file/executable_real.rb +++ b/spec/ruby/shared/file/executable_real.rb @@ -37,6 +37,41 @@ -> { @object.send(@method, nil) }.should raise_error(TypeError) -> { @object.send(@method, false) }.should raise_error(TypeError) end + + platform_is_not :windows do + as_real_superuser do + context "when run by a real superuser" do + before :each do + @file = tmp('temp3.txt') + touch @file + end + + after :each do + rm_r @file + end + + it "returns true if file owner has permission to execute" do + File.chmod(0766, @file) + @object.send(@method, @file).should == true + end + + it "returns true if group has permission to execute" do + File.chmod(0676, @file) + @object.send(@method, @file).should == true + end + + it "returns true if other have permission to execute" do + File.chmod(0667, @file) + @object.send(@method, @file).should == true + end + + it "return false if nobody has permission to execute" do + File.chmod(0666, @file) + @object.send(@method, @file).should == false + end + end + end + end end describe :file_executable_real_missing, shared: true do diff --git a/spec/ruby/shared/file/readable.rb b/spec/ruby/shared/file/readable.rb index eb2ca0681218..7b45e23e3607 100644 --- a/spec/ruby/shared/file/readable.rb +++ b/spec/ruby/shared/file/readable.rb @@ -24,6 +24,22 @@ it "accepts an object that has a #to_path method" do @object.send(@method, mock_to_path(@file2)).should == true end + + platform_is_not :windows do + as_superuser do + context "when run by a superuser" do + it "returns true unconditionally" do + file = tmp('temp.txt') + touch file + + File.chmod(0333, file) + @object.send(@method, file).should == true + + rm_r file + end + end + end + end end describe :file_readable_missing, shared: true do diff --git a/spec/ruby/shared/file/readable_real.rb b/spec/ruby/shared/file/readable_real.rb index b6e53ac76d75..32d38bc7a23f 100644 --- a/spec/ruby/shared/file/readable_real.rb +++ b/spec/ruby/shared/file/readable_real.rb @@ -14,6 +14,22 @@ it "accepts an object that has a #to_path method" do File.open(@file,'w') { @object.send(@method, mock_to_path(@file)).should == true } end + + platform_is_not :windows do + as_real_superuser do + context "when run by a real superuser" do + it "returns true unconditionally" do + file = tmp('temp.txt') + touch file + + File.chmod(0333, file) + @object.send(@method, file).should == true + + rm_r file + end + end + end + end end describe :file_readable_real_missing, shared: true do diff --git a/spec/ruby/shared/file/writable.rb b/spec/ruby/shared/file/writable.rb index 4bb8aedce684..65ea2c1781a6 100644 --- a/spec/ruby/shared/file/writable.rb +++ b/spec/ruby/shared/file/writable.rb @@ -19,6 +19,22 @@ it "accepts an object that has a #to_path method" do File.open(@file,'w') { @object.send(@method, mock_to_path(@file)).should == true } end + + platform_is_not :windows do + as_superuser do + context "when run by a superuser" do + it "returns true unconditionally" do + file = tmp('temp.txt') + touch file + + File.chmod(0555, file) + @object.send(@method, file).should == true + + rm_r file + end + end + end + end end describe :file_writable_missing, shared: true do diff --git a/spec/ruby/shared/file/writable_real.rb b/spec/ruby/shared/file/writable_real.rb index e9721fd379b8..b4a0a58c6e43 100644 --- a/spec/ruby/shared/file/writable_real.rb +++ b/spec/ruby/shared/file/writable_real.rb @@ -24,6 +24,22 @@ -> { @object.send(@method, nil) }.should raise_error(TypeError) -> { @object.send(@method, false) }.should raise_error(TypeError) end + + platform_is_not :windows do + as_real_superuser do + context "when run by a real superuser" do + it "returns true unconditionally" do + file = tmp('temp.txt') + touch file + + File.chmod(0555, file) + @object.send(@method, file).should == true + + rm_r file + end + end + end + end end describe :file_writable_real_missing, shared: true do diff --git a/src/main/ruby/truffleruby/core/stat.rb b/src/main/ruby/truffleruby/core/stat.rb index a214961f3594..0b80a5b223ae 100644 --- a/src/main/ruby/truffleruby/core/stat.rb +++ b/src/main/ruby/truffleruby/core/stat.rb @@ -131,14 +131,14 @@ def directory? end def executable? - return true if superuser? + return mode & S_IXUGO != 0 if superuser? return mode & S_IXUSR != 0 if owned? return mode & S_IXGRP != 0 if grpowned? mode & S_IXOTH != 0 end def executable_real? - return true if rsuperuser? + return mode & S_IXUGO != 0 if rsuperuser? return mode & S_IXUSR != 0 if rowned? return mode & S_IXGRP != 0 if rgrpowned? mode & S_IXOTH != 0