aboutsummaryrefslogtreecommitdiff
path: root/internal/syscallcompat/open_nofollow.go
diff options
context:
space:
mode:
authorJakob Unterwurzacher2017-12-02 20:35:44 +0100
committerJakob Unterwurzacher2017-12-02 20:35:44 +0100
commit91e042e2bafbec3271d8c79005f7dc8229a16a10 (patch)
tree0a49571caf3003ec7c958ed993b96967bb1f0147 /internal/syscallcompat/open_nofollow.go
parent1d289736110f99e63a01449312888e5c3f241656 (diff)
syscallcompat: add OpenNofollow helper
OpenNofollow = symlink-race-safe Open Prepares fixing https://github.com/rfjakob/gocryptfs/issues/165
Diffstat (limited to 'internal/syscallcompat/open_nofollow.go')
-rw-r--r--internal/syscallcompat/open_nofollow.go49
1 files changed, 49 insertions, 0 deletions
diff --git a/internal/syscallcompat/open_nofollow.go b/internal/syscallcompat/open_nofollow.go
new file mode 100644
index 0000000..c804e12
--- /dev/null
+++ b/internal/syscallcompat/open_nofollow.go
@@ -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)
+}