From 2758c75cae2896b7f1327fe00f60a1c017c0e0d1 Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Sat, 10 Dec 2016 12:59:54 +0100 Subject: ctlsock: sanitize paths before passing them to the backend You used to be able to crash gocryptfs by passing "/foo" of "foo/" to the ctlsock. Fixes https://github.com/rfjakob/gocryptfs/issues/66 --- internal/ctlsock/ctlsock_serve.go | 18 ++++++++++++++++-- internal/ctlsock/sanitize.go | 20 ++++++++++++++++++++ internal/ctlsock/sanitize_test.go | 25 +++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 internal/ctlsock/sanitize.go create mode 100644 internal/ctlsock/sanitize_test.go (limited to 'internal/ctlsock') diff --git a/internal/ctlsock/ctlsock_serve.go b/internal/ctlsock/ctlsock_serve.go index 571260d..63515c0 100644 --- a/internal/ctlsock/ctlsock_serve.go +++ b/internal/ctlsock/ctlsock_serve.go @@ -5,6 +5,7 @@ package ctlsock import ( "encoding/json" "errors" + "fmt" "io" "net" "os" @@ -35,6 +36,9 @@ type ResponseStruct struct { ErrNo int32 // ErrText is a detailed error message. ErrText string + // WarnText contains warnings that may have been encountered while + // processing the message. + WarnText string } type ctlSockHandler struct { @@ -102,14 +106,19 @@ func (ch *ctlSockHandler) handleConnection(conn *net.UnixConn) { func (ch *ctlSockHandler) handleRequest(in *RequestStruct, conn *net.UnixConn) { var err error var out ResponseStruct + var inPath, clean string if in.DecryptPath != "" && in.EncryptPath != "" { err = errors.New("Ambigous") } else if in.DecryptPath == "" && in.EncryptPath == "" { err = errors.New("No operation") } else if in.DecryptPath != "" { - out.Result, err = ch.fs.DecryptPath(in.DecryptPath) + inPath = in.DecryptPath + clean = SanitizePath(inPath) + out.Result, err = ch.fs.DecryptPath(clean) } else if in.EncryptPath != "" { - out.Result, err = ch.fs.EncryptPath(in.EncryptPath) + inPath = in.EncryptPath + clean = SanitizePath(inPath) + out.Result, err = ch.fs.EncryptPath(clean) } if err != nil { out.ErrText = err.Error() @@ -121,6 +130,9 @@ func (ch *ctlSockHandler) handleRequest(in *RequestStruct, conn *net.UnixConn) { } } } + if inPath != clean { + out.WarnText = fmt.Sprintf("Non-canonical input path %q has been interpreted as %q", inPath, clean) + } sendResponse(&out, conn) } @@ -130,6 +142,8 @@ func sendResponse(msg *ResponseStruct, conn *net.UnixConn) { tlog.Warn.Printf("ctlsock: Marshal failed: %v", err) return } + // For convenience for the user, add a newline at the end. + jsonMsg = append(jsonMsg, '\n') _, err = conn.Write(jsonMsg) if err != nil { tlog.Warn.Printf("ctlsock: Write failed: %v", err) diff --git a/internal/ctlsock/sanitize.go b/internal/ctlsock/sanitize.go new file mode 100644 index 0000000..5bc3706 --- /dev/null +++ b/internal/ctlsock/sanitize.go @@ -0,0 +1,20 @@ +package ctlsock + +import ( + "path/filepath" +) + +// SanitizePath adapts filepath.Clean for FUSE paths. +// 1) It always returns a relative path +// 2) It returns "" instead of "." +// See the TestSanitizePath testcases for examples. +func SanitizePath(path string) string { + clean := filepath.Clean(path) + if clean == "." || clean == "/" { + return "" + } + if clean[0] == '/' { + clean = clean[1:] + } + return clean +} diff --git a/internal/ctlsock/sanitize_test.go b/internal/ctlsock/sanitize_test.go new file mode 100644 index 0000000..dfcb62c --- /dev/null +++ b/internal/ctlsock/sanitize_test.go @@ -0,0 +1,25 @@ +package ctlsock + +import ( + "testing" +) + +func TestSanitizePath(t *testing.T) { + testCases := [][]string{ + {"", ""}, + {".", ""}, + {"/", ""}, + {"foo", "foo"}, + {"/foo", "foo"}, + {"foo/", "foo"}, + {"/foo/", "foo"}, + {"/foo/./foo", "foo/foo"}, + {"./", ""}, + } + for _, tc := range testCases { + res := SanitizePath(tc[0]) + if res != tc[1] { + t.Errorf("%q: got %q, want %q", tc[0], res, tc[1]) + } + } +} -- cgit v1.2.3