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
This commit is contained in:
Jakob Unterwurzacher 2016-12-10 12:59:54 +01:00
parent 21904cd5f0
commit 2758c75cae
4 changed files with 68 additions and 2 deletions

View File

@ -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)

View File

@ -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
}

View File

@ -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])
}
}
}

View File

@ -57,6 +57,13 @@ func TestCtlSock(t *testing.T) {
if response.ErrNo != int32(syscall.ENOENT) || response.Result != "" {
t.Errorf("incorrect error handling: %+v", response)
}
// Strange paths should not cause a crash
crashers := []string{"/foo", "foo/", "/foo/", ".", "/////", "/../../."}
for _, c := range crashers {
req.EncryptPath = c
// QueryCtlSock calls t.Fatal if it gets EOF when gocryptfs panics
test_helpers.QueryCtlSock(t, sock, req)
}
}
// In gocryptfs before v1.2, the file header was only read once for each