Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions file_handling_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"os"
"path/filepath"
"testing"
)
Expand Down Expand Up @@ -77,3 +78,18 @@ func TestNewFile(t *testing.T) {
})
}
}

func TestHasSpecialFileModeBits(t *testing.T) {
if !hasSpecialFileModeBits(os.ModeSetuid | 0o755) {
t.Error("expected setuid bit to be detected")
}
if !hasSpecialFileModeBits(os.ModeSetgid | 0o755) {
t.Error("expected setgid bit to be detected")
}
if !hasSpecialFileModeBits(os.ModeSticky | 0o755) {
t.Error("expected sticky bit to be detected")
}
if hasSpecialFileModeBits(0o644) {
t.Error("expected plain mode to pass through")
}
}
9 changes: 9 additions & 0 deletions find_replace.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,15 @@ func (fr *findReplace) RenameFile(f *File) error {
// ReplaceContents rewrites the file at f if its contents contain the find
// string. Binary-looking files (where Read returns "") are skipped silently.
func (fr *findReplace) ReplaceContents(f *File) error {
mode, err := f.Mode()
if err != nil {
return err
}
if hasSpecialFileModeBits(mode) {
log.Printf("Skipping rewrite of %v: setuid, setgid, or sticky bit set", f.Path)
return nil
}

content, err := f.Read()
if err != nil {
return err
Expand Down
41 changes: 41 additions & 0 deletions find_replace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,47 @@ func withWorkingDir(t *testing.T, dir string) {
t.Cleanup(func() { _ = os.Chdir(prev) })
}


func TestReplaceContentsSkipsSetuidFile(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("special mode bits not applicable")
}

dir := t.TempDir()
path := filepath.Join(dir, "setuid.txt")
if err := os.WriteFile(path, []byte("needle"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.Chmod(path, 0o4755); err != nil {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Set the setuid bit with os.ModeSetuid

In this test, 0o4755 does not request setuid through Go's os.Chmod: on Unix, Chmod consumes mode.Perm() plus os.ModeSetuid/os.ModeSetgid/os.ModeSticky, and 0o4000 is neither a permission bit nor os.ModeSetuid. On normal Unix this leaves the file at 0755, so the later hasSpecialFileModeBits check takes the t.Skip path and the new regression test never exercises the skip behavior; use os.ModeSetuid|0o755 so the test actually covers setuid files.

Useful? React with 👍 / 👎.

t.Fatal(err)
}

f, err := NewFile(path)
if err != nil {
t.Fatal(err)
}
mode, err := f.Mode()
if err != nil {
t.Fatal(err)
}
if !hasSpecialFileModeBits(mode) {
t.Skip("setuid bit not supported in this environment")
}

fr := findReplace{find: "needle", replace: "hay"}
if err := fr.ReplaceContents(f); err != nil {
t.Fatal(err)
}

got, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
if string(got) != "needle" {
t.Fatalf("content = %q; want unchanged %q", got, "needle")
}
}

func CloneRepoToTestDir(b *testing.B, repoUrl string) *File {
b.Helper()
d := newTestDir(b, "", "*")
Expand Down
7 changes: 7 additions & 0 deletions mode_bits.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

import "os"

func hasSpecialFileModeBits(mode os.FileMode) bool {
return mode&os.ModeSetuid != 0 || mode&os.ModeSetgid != 0 || mode&os.ModeSticky != 0
}