Add checks for unix socket destination to avoid FDK tricking agent into talking to non-relative dirs (#1247)

* Add checks for unix socket destination to avoid leaking access to host OS

* style, typos
This commit is contained in:
Owen Cliffe
2018-09-28 02:20:03 +01:00
committed by Reed Allman
parent d011f58163
commit 53d4be00ca
2 changed files with 100 additions and 0 deletions

View File

@@ -25,6 +25,7 @@ import (
"github.com/sirupsen/logrus"
"go.opencensus.io/stats"
"go.opencensus.io/trace"
"os"
)
// TODO we should prob store async calls in db immediately since we're returning id (will 404 until post-execution)
@@ -1011,6 +1012,7 @@ func (a *agent) runHot(ctx context.Context, call *call, tok ResourceToken, state
call.slots.queueSlot(&hotSlot{done: make(chan struct{}), fatalErr: err})
return
}
case <-ctx.Done():
call.slots.queueSlot(&hotSlot{done: make(chan struct{}), fatalErr: ctx.Err()})
return
@@ -1055,9 +1057,40 @@ func (a *agent) runHot(ctx context.Context, call *call, tok ResourceToken, state
logger.WithError(res.Error()).Info("hot function terminated")
}
//checkSocketDestination verifies that the socket file created by the FDK is valid and permitted - notably verifying that any symlinks are relative to the socket dir
func checkSocketDestination(filename string) error {
finfo, err := os.Lstat(filename)
if err != nil {
return fmt.Errorf("error statting unix socket link file %s", err)
}
if (finfo.Mode() & os.ModeSymlink) > 0 {
linkDest, err := os.Readlink(filename)
if err != nil {
return fmt.Errorf("error reading unix socket symlink destination %s", err)
}
if filepath.Dir(linkDest) != "." {
return fmt.Errorf("invalid unix socket symlink, symlinks must be relative within the unix socket directory")
}
}
// stat the absolute path and check it is a socket
absInfo, err := os.Stat(filename)
if err != nil {
return fmt.Errorf("unable to stat unix socket file %s", err)
}
if absInfo.Mode()&os.ModeSocket == 0 {
return fmt.Errorf("listener file is not a socket")
}
return nil
}
func inotifyUDS(ctx context.Context, iofsDir string, awaitUDS chan<- error) {
// XXX(reed): I forgot how to plumb channels temporarily forgive me for this sin (inotify will timeout, this is just bad programming)
err := inotifyAwait(ctx, iofsDir)
if err == nil {
err = checkSocketDestination(filepath.Join(iofsDir, udsFilename))
}
select {
case awaitUDS <- err:
case <-ctx.Done():
@@ -1089,6 +1122,7 @@ func inotifyAwait(ctx context.Context, iofsDir string) error {
case event := <-fsWatcher.Events:
common.Logger(ctx).WithField("event", event).Debug("fsnotify event")
if event.Op&fsnotify.Create == fsnotify.Create && event.Name == filepath.Join(iofsDir, udsFilename) {
// wait until the socket file is created by the container
return nil
}

View File

@@ -22,6 +22,10 @@ import (
"github.com/fnproject/fn/api/models"
"github.com/fnproject/fn/api/mqs"
"github.com/sirupsen/logrus"
"io/ioutil"
"net"
"os"
"path/filepath"
)
func init() {
@@ -1162,3 +1166,65 @@ func TestDockerAuthExtn(t *testing.T) {
t.Fatalf("unexpected registry token %s", da.RegistryToken)
}
}
func TestCheckSocketDestination(t *testing.T) {
tmpDir, err := ioutil.TempDir(os.TempDir(), "testSocketPerms")
if err != nil {
t.Fatal("failed to create temp tmpDir", err)
}
defer os.RemoveAll(tmpDir)
goodSock := filepath.Join(tmpDir, "fn.sock")
s, err := net.Listen("unix", goodSock)
if err != nil {
t.Fatal("failed to create socket", err)
}
defer s.Close()
err = os.Chmod(goodSock, 0666)
if err != nil {
t.Fatal("failed to change perms", err)
}
notASocket := filepath.Join(tmpDir, "notasock.sock")
err = ioutil.WriteFile(notASocket, []byte{0}, 0666)
if err != nil {
t.Fatalf("Failed to create empty sock")
}
goodSymlink := filepath.Join(tmpDir, "goodlink.sock")
err = os.Symlink("fn.sock", goodSymlink)
if err != nil {
t.Fatalf("Failed to create symlink")
}
badLinkNonExistant := filepath.Join(tmpDir, "badlinknonExist.sock")
err = os.Symlink("noxexistatnt.sock", badLinkNonExistant)
if err != nil {
t.Fatalf("Failed to create symlink")
}
badLinkOutOfPath := filepath.Join(tmpDir, "badlinkoutofpath.sock")
err = os.Symlink(filepath.Join("..", filepath.Base(tmpDir), "fn.sock"), badLinkOutOfPath)
if err != nil {
t.Fatalf("Failed to create symlink")
}
for _, good := range []string{goodSock, goodSymlink} {
t.Run(filepath.Base(good), func(t *testing.T) {
err := checkSocketDestination(good)
if err != nil {
t.Errorf("Expected no error got, %s", err)
}
})
}
for _, bad := range []string{notASocket, badLinkNonExistant, badLinkOutOfPath, filepath.Join(tmpDir, "notAFile"), tmpDir} {
t.Run(filepath.Base(bad), func(t *testing.T) {
err := checkSocketDestination(bad)
if err == nil {
t.Errorf("Expected an error but got none")
}
})
}
}