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:
parent
21904cd5f0
commit
2758c75cae
@ -5,6 +5,7 @@ package ctlsock
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
@ -35,6 +36,9 @@ type ResponseStruct struct {
|
|||||||
ErrNo int32
|
ErrNo int32
|
||||||
// ErrText is a detailed error message.
|
// ErrText is a detailed error message.
|
||||||
ErrText string
|
ErrText string
|
||||||
|
// WarnText contains warnings that may have been encountered while
|
||||||
|
// processing the message.
|
||||||
|
WarnText string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ctlSockHandler struct {
|
type ctlSockHandler struct {
|
||||||
@ -102,14 +106,19 @@ func (ch *ctlSockHandler) handleConnection(conn *net.UnixConn) {
|
|||||||
func (ch *ctlSockHandler) handleRequest(in *RequestStruct, conn *net.UnixConn) {
|
func (ch *ctlSockHandler) handleRequest(in *RequestStruct, conn *net.UnixConn) {
|
||||||
var err error
|
var err error
|
||||||
var out ResponseStruct
|
var out ResponseStruct
|
||||||
|
var inPath, clean string
|
||||||
if in.DecryptPath != "" && in.EncryptPath != "" {
|
if in.DecryptPath != "" && in.EncryptPath != "" {
|
||||||
err = errors.New("Ambigous")
|
err = errors.New("Ambigous")
|
||||||
} else if in.DecryptPath == "" && in.EncryptPath == "" {
|
} else if in.DecryptPath == "" && in.EncryptPath == "" {
|
||||||
err = errors.New("No operation")
|
err = errors.New("No operation")
|
||||||
} else if in.DecryptPath != "" {
|
} 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 != "" {
|
} 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 {
|
if err != nil {
|
||||||
out.ErrText = err.Error()
|
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)
|
sendResponse(&out, conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,6 +142,8 @@ func sendResponse(msg *ResponseStruct, conn *net.UnixConn) {
|
|||||||
tlog.Warn.Printf("ctlsock: Marshal failed: %v", err)
|
tlog.Warn.Printf("ctlsock: Marshal failed: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// For convenience for the user, add a newline at the end.
|
||||||
|
jsonMsg = append(jsonMsg, '\n')
|
||||||
_, err = conn.Write(jsonMsg)
|
_, err = conn.Write(jsonMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tlog.Warn.Printf("ctlsock: Write failed: %v", err)
|
tlog.Warn.Printf("ctlsock: Write failed: %v", err)
|
||||||
|
20
internal/ctlsock/sanitize.go
Normal file
20
internal/ctlsock/sanitize.go
Normal 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
|
||||||
|
}
|
25
internal/ctlsock/sanitize_test.go
Normal file
25
internal/ctlsock/sanitize_test.go
Normal 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])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -57,6 +57,13 @@ func TestCtlSock(t *testing.T) {
|
|||||||
if response.ErrNo != int32(syscall.ENOENT) || response.Result != "" {
|
if response.ErrNo != int32(syscall.ENOENT) || response.Result != "" {
|
||||||
t.Errorf("incorrect error handling: %+v", response)
|
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
|
// In gocryptfs before v1.2, the file header was only read once for each
|
||||||
|
Loading…
x
Reference in New Issue
Block a user