-
Notifications
You must be signed in to change notification settings - Fork 257
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
syscallcompat: add OpenNofollow helper
OpenNofollow = symlink-race-safe Open Prepares fixing #165
- Loading branch information
Showing
2 changed files
with
86 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package syscallcompat | ||
|
||
import ( | ||
"path/filepath" | ||
"strings" | ||
"syscall" | ||
|
||
"github.com/rfjakob/gocryptfs/internal/tlog" | ||
) | ||
|
||
// OpenNofollow opens the file/dir at "relPath" in a way that is secure against | ||
// symlink attacks. Symlinks that are part of "relPath" are never followed. | ||
// This function is implemented by walking the directory tree, starting at | ||
// "baseDir", using the Openat syscall with the O_NOFOLLOW flag. | ||
// Symlinks that are part of the "baseDir" path are followed. | ||
func OpenNofollow(baseDir string, relPath string, flags int, mode uint32) (fd int, err error) { | ||
if !filepath.IsAbs(baseDir) { | ||
tlog.Warn.Printf("BUG: OpenNofollow called with relative baseDir=%q", baseDir) | ||
return -1, syscall.EINVAL | ||
} | ||
if filepath.IsAbs(relPath) { | ||
tlog.Warn.Printf("BUG: OpenNofollow called with absolute relPath=%q", relPath) | ||
return -1, syscall.EINVAL | ||
} | ||
// Open the base dir | ||
dirfd, err := syscall.Open(baseDir, syscall.O_RDONLY, 0) | ||
if err != nil { | ||
return -1, err | ||
} | ||
// Split the path into components and separate intermediate directories | ||
// and the final basename | ||
parts := strings.Split(relPath, "/") | ||
dirs := parts[:len(parts)-1] | ||
final := parts[len(parts)-1] | ||
// Walk intermediate directories | ||
var dirfd2 int | ||
for _, name := range dirs { | ||
dirfd2, err = Openat(dirfd, name, syscall.O_RDONLY|syscall.O_NOFOLLOW, 0) | ||
syscall.Close(dirfd) | ||
if err != nil { | ||
return -1, err | ||
} | ||
dirfd = dirfd2 | ||
} | ||
defer syscall.Close(dirfd) | ||
// Open the final component with the flags and permissions requested by | ||
// the user plus forced NOFOLLOW. | ||
return Openat(dirfd, final, flags|syscall.O_NOFOLLOW, mode) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package syscallcompat | ||
|
||
import ( | ||
"os" | ||
"syscall" | ||
"testing" | ||
) | ||
|
||
func TestOpenNofollow(t *testing.T) { | ||
err := os.MkdirAll(tmpDir+"/d1/d2/d3", 0700) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
// Create a file | ||
fd, err := OpenNofollow(tmpDir, "d1/d2/d3/f1", syscall.O_RDWR|syscall.O_CREAT|syscall.O_EXCL, 0600) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
syscall.Close(fd) | ||
_, err = os.Stat(tmpDir + "/d1/d2/d3/f1") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
// Replace "d1" with a symlink - open should fail with ELOOP | ||
err = os.Rename(tmpDir+"/d1", tmpDir+"/d1.renamed") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
os.Symlink(tmpDir+"/d1.renamed", tmpDir+"/d1") | ||
fd, err = OpenNofollow(tmpDir, "d1/d2/d3/f1", syscall.O_RDWR|syscall.O_CREAT, 0600) | ||
if err == nil { | ||
t.Fatalf("should have failed") | ||
} | ||
if err != syscall.ELOOP { | ||
t.Errorf("expected ELOOP, got %v", err) | ||
} | ||
} |