isula-build/patch/0073-cleancode-fix-problems-found-by-code-review.patch

584 lines
20 KiB
Diff
Raw Normal View History

From 6ee8705ae63ec9918f2cc19b1d903c5cdb0d5487 Mon Sep 17 00:00:00 2001
From: DCCooper <1866858@gmail.com>
Date: Mon, 30 Nov 2020 19:40:14 +0800
Subject: [PATCH 3/4] cleancode:fix problems found by code review
Signed-off-by: DCCooper <1866858@gmail.com>
---
Makefile | 1 +
builder/dockerfile/add_copy.go | 5 ++++-
builder/dockerfile/builder.go | 7 +++++--
cmd/cli/build.go | 18 ++++++++++--------
cmd/cli/build_test.go | 6 ++----
cmd/daemon/main.go | 34 +++++++++++++++++++++++++++-------
daemon/daemon.go | 15 +++++++++++++--
daemon/import.go | 8 ++++++--
daemon/load.go | 8 +++++---
daemon/login.go | 10 +++++++---
daemon/logout.go | 13 ++++++++-----
exporter/common.go | 25 +++++++++++++++----------
store/store.go | 8 ++++----
util/common.go | 12 ++++++++++++
util/common_test.go | 18 +++++++++++++++---
15 files changed, 134 insertions(+), 54 deletions(-)
diff --git a/Makefile b/Makefile
index f40941b4..cbace592 100644
--- a/Makefile
+++ b/Makefile
@@ -97,6 +97,7 @@ proto:
install:
install -D -m0551 bin/isula-build $(BINDIR)
install -D -m0550 bin/isula-builder $(BINDIR)
+ @( getent group isula > /dev/null ) || ( groupadd --system isula )
@[ ! -d ${CONFIG_DIR}/${CONFIG_FILE} ] && install -dm0650 ${CONFIG_DIR}
@( [ -f ${CONFIG_DIR}/${CONFIG_FILE} ] && printf "%-20s %s\n" "${CONFIG_FILE}" "already exist in ${CONFIG_DIR}, please replace it manually." ) || install -D -m0600 ${LOCAL_CONF_PREFIX}/${CONFIG_FILE} ${CONFIG_DIR}/${CONFIG_FILE}
@( [ -f ${CONFIG_DIR}/${POLICY_FILE} ] && printf "%-20s %s\n" "${POLICY_FILE}" "already exist in ${CONFIG_DIR}, please replace it manually." ) || install -D -m0600 ${LOCAL_CONF_PREFIX}/${POLICY_FILE} ${CONFIG_DIR}/${POLICY_FILE}
diff --git a/builder/dockerfile/add_copy.go b/builder/dockerfile/add_copy.go
index c5d966e2..42cda7f9 100644
--- a/builder/dockerfile/add_copy.go
+++ b/builder/dockerfile/add_copy.go
@@ -143,7 +143,10 @@ func (c *cmdBuilder) getCopyContextDir(from string) (string, func(), error) {
if err != nil {
return "", nil, err
}
- c.stage.buildOpt.systemContext.DockerCertPath = filepath.Join(constant.DefaultCertRoot, server)
+ c.stage.buildOpt.systemContext.DockerCertPath, err = securejoin.SecureJoin(constant.DefaultCertRoot, server)
+ if err != nil {
+ return "", nil, err
+ }
// "from" is neither name nor index of stage, consider that "from" is image description
imgDesc, err := prepareImage(&image.PrepareImageOptions{
diff --git a/builder/dockerfile/builder.go b/builder/dockerfile/builder.go
index 757f4cd5..7e954f0d 100644
--- a/builder/dockerfile/builder.go
+++ b/builder/dockerfile/builder.go
@@ -22,13 +22,13 @@ import (
"io"
"io/ioutil"
"os"
- "path/filepath"
"regexp"
"sort"
"strings"
"time"
"github.com/containers/image/v5/docker/reference"
+ securejoin "github.com/cyphar/filepath-securejoin"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -257,7 +257,10 @@ func (b *Builder) newStageBuilders() error {
if err != nil {
return err
}
- sb.buildOpt.systemContext.DockerCertPath = filepath.Join(constant.DefaultCertRoot, server)
+ sb.buildOpt.systemContext.DockerCertPath, err = securejoin.SecureJoin(constant.DefaultCertRoot, server)
+ if err != nil {
+ return err
+ }
b.stageBuilders = append(b.stageBuilders, sb)
}
diff --git a/cmd/cli/build.go b/cmd/cli/build.go
index f2a49e02..3ebbb90f 100644
--- a/cmd/cli/build.go
+++ b/cmd/cli/build.go
@@ -171,6 +171,13 @@ func newBuildOptions(args []string) error {
return nil
}
+ // check cap list
+ for _, c := range buildOpts.capAddList {
+ if !util.CheckCap(c) {
+ return errors.Errorf("cap %v is invalid", c)
+ }
+ }
+
// the path may be a symbol link
contextDir, err := filepath.Abs(args[0])
if err != nil {
@@ -236,8 +243,9 @@ func modifyLocalTransporter(transport string, absPath string, segments []string)
const validIsuladFieldsLen = 3
switch transport {
case "docker-archive":
- segments[1] = absPath
- buildOpts.output = strings.Join(segments, ":")
+ newSeg := util.CopyStrings(segments)
+ newSeg[1] = absPath
+ buildOpts.output = strings.Join(newSeg, ":")
return nil
case "isulad":
if len(segments) != validIsuladFieldsLen {
@@ -311,12 +319,6 @@ func runBuild(ctx context.Context, cli Cli) (string, error) {
digest string
)
- for _, c := range buildOpts.capAddList {
- if !util.CheckCap(c) {
- return "", errors.Errorf("cap %v is invalid", c)
- }
- }
-
if err = checkAndProcessOutput(); err != nil {
return "", err
}
diff --git a/cmd/cli/build_test.go b/cmd/cli/build_test.go
index 1fa8ecc4..b4c3a61f 100644
--- a/cmd/cli/build_test.go
+++ b/cmd/cli/build_test.go
@@ -602,17 +602,15 @@ func TestRunBuildWithCap(t *testing.T) {
defer tmpDir.Remove()
buildOpts.file = tmpDir.Join("Dockerfile")
buildOpts.output = "docker-daemon:cap:latest"
- mockBuild := newMockDaemon()
- ctx := context.Background()
- cli := newMockClient(&mockGrpcClient{imageBuildFunc: mockBuild.build})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
buildOpts.capAddList = tt.caps
- _, err := runBuild(ctx, &cli)
+ err := newBuildOptions([]string{tmpDir.Path()})
if tt.isErr {
assert.ErrorContains(t, err, "is invalid")
}
+ buildOpts.capAddList = nil
})
}
}
diff --git a/cmd/daemon/main.go b/cmd/daemon/main.go
index 9fcde5cd..d6f1d080 100644
--- a/cmd/daemon/main.go
+++ b/cmd/daemon/main.go
@@ -22,6 +22,7 @@ import (
"github.com/BurntSushi/toml"
"github.com/containers/storage/pkg/reexec"
+ securejoin "github.com/cyphar/filepath-securejoin"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
@@ -157,9 +158,17 @@ func before(cmd *cobra.Command) error {
logrus.SetOutput(os.Stdout)
logrus.SetFormatter(&logrus.TextFormatter{FullTimestamp: true})
+ runRoot, err := securejoin.SecureJoin(daemonOpts.RunRoot, "storage")
+ if err != nil {
+ return err
+ }
+ dataRoot, err := securejoin.SecureJoin(daemonOpts.DataRoot, "storage")
+ if err != nil {
+ return err
+ }
store.SetDefaultStoreOptions(store.DaemonStoreOptions{
- RunRoot: filepath.Join(daemonOpts.RunRoot, "storage"),
- DataRoot: filepath.Join(daemonOpts.DataRoot, "storage"),
+ RunRoot: runRoot,
+ DataRoot: dataRoot,
Driver: daemonOpts.StorageDriver,
DriverOption: util.CopyStrings(daemonOpts.StorageOpts),
})
@@ -204,7 +213,7 @@ func loadConfig(path string) (config.TomlConfig, error) {
return conf, err
}
-func mergeStorageConfig(cmd *cobra.Command) {
+func mergeStorageConfig(cmd *cobra.Command) error {
store.SetDefaultConfigFilePath(constant.StorageConfigPath)
option, err := store.GetDefaultStoreOptions(true)
if err == nil {
@@ -218,10 +227,16 @@ func mergeStorageConfig(cmd *cobra.Command) {
var storeOpt store.DaemonStoreOptions
if option.RunRoot == "" {
- storeOpt.RunRoot = filepath.Join(daemonOpts.RunRoot, "storage")
+ storeOpt.RunRoot, err = securejoin.SecureJoin(daemonOpts.RunRoot, "storage")
+ if err != nil {
+ return err
+ }
}
if option.GraphRoot == "" {
- storeOpt.DataRoot = filepath.Join(daemonOpts.DataRoot, "storage")
+ storeOpt.DataRoot, err = securejoin.SecureJoin(daemonOpts.DataRoot, "storage")
+ if err != nil {
+ return err
+ }
}
if daemonOpts.StorageDriver != "" {
storeOpt.Driver = daemonOpts.StorageDriver
@@ -230,6 +245,8 @@ func mergeStorageConfig(cmd *cobra.Command) {
storeOpt.DriverOption = util.CopyStrings(daemonOpts.StorageOpts)
}
store.SetDefaultStoreOptions(storeOpt)
+
+ return nil
}
func mergeConfig(conf config.TomlConfig, cmd *cobra.Command) {
@@ -258,7 +275,10 @@ func setupWorkingDirectories() error {
return errors.Errorf("runroot(%q) and dataroot(%q) must be different paths", daemonOpts.RunRoot, daemonOpts.DataRoot)
}
- buildTmpDir := filepath.Join(daemonOpts.DataRoot, dataRootTmpDirPrefix)
+ buildTmpDir, err := securejoin.SecureJoin(daemonOpts.DataRoot, dataRootTmpDirPrefix)
+ if err != nil {
+ return err
+ }
dirs := []string{daemonOpts.DataRoot, daemonOpts.RunRoot, buildTmpDir}
for _, dir := range dirs {
if !filepath.IsAbs(dir) {
@@ -325,7 +345,7 @@ func checkAndValidateConfig(cmd *cobra.Command) error {
// if storage config file exists, merge storage config
if util.IsExist(constant.StorageConfigPath) {
- mergeStorageConfig(cmd)
+ return mergeStorageConfig(cmd)
}
return nil
diff --git a/daemon/daemon.go b/daemon/daemon.go
index 1237f35d..90ccf648 100644
--- a/daemon/daemon.go
+++ b/daemon/daemon.go
@@ -23,6 +23,7 @@ import (
"time"
"github.com/containerd/containerd/sys/reaper"
+ securejoin "github.com/cyphar/filepath-securejoin"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
@@ -130,10 +131,20 @@ func (d *Daemon) Run() (err error) {
// NewBuilder returns the builder with request sent from GRPC service
func (d *Daemon) NewBuilder(ctx context.Context, req *pb.BuildRequest) (b builder.Builder, err error) {
+ var (
+ buildDir string
+ runDir string
+ )
// buildDir is used to set directory which is used to store tmp data
- buildDir := filepath.Join(d.opts.DataRoot, dataRootTmpDirPrefix, req.BuildID)
+ buildDir, err = securejoin.SecureJoin(d.opts.DataRoot, filepath.Join(dataRootTmpDirPrefix, req.BuildID))
+ if err != nil {
+ return nil, err
+ }
// runDir is used to store such as container bundle directories
- runDir := filepath.Join(d.opts.RunRoot, req.BuildID)
+ runDir, err = securejoin.SecureJoin(d.opts.RunRoot, req.BuildID)
+ if err != nil {
+ return nil, err
+ }
// this key with BuildDir will be used by exporter to save blob temporary
// NOTE: keep it be updated before NewBuilder. ctx will be taken by Builder
diff --git a/daemon/import.go b/daemon/import.go
index a72d732e..21ffeaa3 100644
--- a/daemon/import.go
+++ b/daemon/import.go
@@ -22,6 +22,7 @@ import (
"github.com/containers/image/v5/tarball"
"github.com/containers/image/v5/transports"
"github.com/containers/image/v5/types"
+ securejoin "github.com/cyphar/filepath-securejoin"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"
@@ -43,6 +44,7 @@ func (b *Backend) Import(req *pb.ImportRequest, stream pb.Control_ImportServer)
source = req.Source
reference = req.Reference
importID = req.ImportID
+ tmpDir string
)
logEntry := logrus.WithFields(logrus.Fields{"ImportID": importID})
logEntry.Info("ImportRequest received")
@@ -78,8 +80,10 @@ func (b *Backend) Import(req *pb.ImportRequest, stream pb.Control_ImportServer)
log := logger.NewCliLogger(constant.CliLogBufferLen)
imageCopyOptions := image.NewImageCopyOptions(log)
-
- tmpDir := filepath.Join(b.daemon.opts.DataRoot, dataRootTmpDirPrefix, importID)
+ tmpDir, err = securejoin.SecureJoin(b.daemon.opts.DataRoot, filepath.Join(dataRootTmpDirPrefix, importID))
+ if err != nil {
+ return err
+ }
if err = os.MkdirAll(tmpDir, constant.DefaultRootDirMode); err != nil {
logEntry.Error(err)
return err
diff --git a/daemon/load.go b/daemon/load.go
index d3f9bf6e..1f4c2f87 100644
--- a/daemon/load.go
+++ b/daemon/load.go
@@ -14,10 +14,9 @@
package daemon
import (
- "path/filepath"
-
"github.com/containers/image/v5/docker/tarfile"
"github.com/containers/storage"
+ securejoin "github.com/cyphar/filepath-securejoin"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"
@@ -85,7 +84,10 @@ func (b *Backend) Load(req *pb.LoadRequest, stream pb.Control_LoadServer) error
func getRepoTagFromImageTar(dataRoot, path string) ([]string, error) {
// tmp dir will be removed after NewSourceFromFileWithContext
- tmpDir := filepath.Join(dataRoot, dataRootTmpDirPrefix)
+ tmpDir, err := securejoin.SecureJoin(dataRoot, dataRootTmpDirPrefix)
+ if err != nil {
+ return nil, err
+ }
systemContext := image.GetSystemContext()
systemContext.BigFilesTemporaryDir = tmpDir
diff --git a/daemon/login.go b/daemon/login.go
index 012816a4..e3399983 100644
--- a/daemon/login.go
+++ b/daemon/login.go
@@ -16,11 +16,11 @@ package daemon
import (
"context"
"crypto"
- "path/filepath"
"github.com/containers/image/v5/docker"
"github.com/containers/image/v5/pkg/docker/config"
"github.com/containers/image/v5/types"
+ securejoin "github.com/cyphar/filepath-securejoin"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -48,12 +48,16 @@ func (b *Backend) Login(ctx context.Context, req *pb.LoginRequest) (*pb.LoginRes
"Username": req.GetUsername(),
}).Info("LoginRequest received")
- if err := validLoginOpts(req); err != nil {
+ err := validLoginOpts(req)
+ if err != nil {
return &pb.LoginResponse{Content: loginFailed}, err
}
sysCtx := image.GetSystemContext()
- sysCtx.DockerCertPath = filepath.Join(constant.DefaultCertRoot, req.Server)
+ sysCtx.DockerCertPath, err = securejoin.SecureJoin(constant.DefaultCertRoot, req.Server)
+ if err != nil {
+ return &pb.LoginResponse{Content: loginFailed}, err
+ }
if loginWithAuthFile(req) {
auth, err := config.GetCredentials(sysCtx, req.Server)
diff --git a/daemon/logout.go b/daemon/logout.go
index 82c5beac..355b1f7a 100644
--- a/daemon/logout.go
+++ b/daemon/logout.go
@@ -16,10 +16,10 @@ package daemon
import (
"context"
"fmt"
- "path/filepath"
"strings"
"github.com/containers/image/v5/pkg/docker/config"
+ securejoin "github.com/cyphar/filepath-securejoin"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -35,12 +35,16 @@ func (b *Backend) Logout(ctx context.Context, req *pb.LogoutRequest) (*pb.Logout
"All": req.GetAll(),
}).Info("LogoutRequest received")
- if err := validLogoutOpts(req); err != nil {
+ err := validLogoutOpts(req)
+ if err != nil {
return &pb.LogoutResponse{Result: "Logout Failed"}, err
}
sysCtx := image.GetSystemContext()
- sysCtx.DockerCertPath = filepath.Join(constant.DefaultCertRoot, req.Server)
+ sysCtx.DockerCertPath, err = securejoin.SecureJoin(constant.DefaultCertRoot, req.Server)
+ if err != nil {
+ return &pb.LogoutResponse{Result: "Logout Failed"}, err
+ }
if req.All {
if err := config.RemoveAllAuthentication(sysCtx); err != nil {
@@ -51,8 +55,7 @@ func (b *Backend) Logout(ctx context.Context, req *pb.LogoutRequest) (*pb.Logout
return &pb.LogoutResponse{Result: "Removed authentications"}, nil
}
- err := config.RemoveAuthentication(sysCtx, req.Server)
- if err == nil {
+ if err = config.RemoveAuthentication(sysCtx, req.Server); err == nil {
msg := fmt.Sprintf("Removed authentication for %s", req.Server)
logrus.Infof("Success logout from server: %q", req.Server)
return &pb.LogoutResponse{Result: msg}, nil
diff --git a/exporter/common.go b/exporter/common.go
index 1953b4e1..6e70a38e 100644
--- a/exporter/common.go
+++ b/exporter/common.go
@@ -20,7 +20,6 @@ import (
"io"
"os"
"os/exec"
- "path/filepath"
"strings"
cp "github.com/containers/image/v5/copy"
@@ -30,6 +29,7 @@ import (
"github.com/containers/image/v5/types"
"github.com/containers/storage/pkg/archive"
"github.com/containers/storage/pkg/stringid"
+ securejoin "github.com/cyphar/filepath-securejoin"
"github.com/docker/distribution/reference"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
@@ -72,15 +72,12 @@ func Export(src, destSpec string, opts ExportOptions, localStore *store.Store) e
if err != nil {
return err
}
- opts.SystemContext.DockerCertPath = filepath.Join(constant.DefaultCertRoot, registry)
-
- options := NewCopyOptions(opts)
-
- policyContext, err := NewPolicyContext(opts.SystemContext)
+ opts.SystemContext.DockerCertPath, err = securejoin.SecureJoin(constant.DefaultCertRoot, registry)
if err != nil {
return err
}
- ref, digest, err := export(opts, epter, policyContext, options)
+
+ ref, digest, err := export(epter, opts)
if err != nil {
return errors.Errorf("export image from %s to %s failed, got error: %s", src, destSpec, err)
}
@@ -117,13 +114,18 @@ func exportToIsulad(ctx context.Context, tarPath string) error {
return nil
}
-func export(exOpts ExportOptions, e Exporter, policyContext *signature.PolicyContext, cpOpts *cp.Options) (reference.Canonical, digest.Digest, error) {
+func export(e Exporter, exOpts ExportOptions) (reference.Canonical, digest.Digest, error) {
var (
- err error
ref reference.Canonical
manifestBytes []byte
manifestDigest digest.Digest
)
+
+ cpOpts := NewCopyOptions(exOpts)
+ policyContext, err := NewPolicyContext(exOpts.SystemContext)
+ if err != nil {
+ return nil, "", err
+ }
defer func() {
destroyErr := policyContext.Destroy()
if err == nil {
@@ -176,7 +178,10 @@ func parseExporter(opts ExportOptions, src, destSpec string, localStore *store.S
// 3. get dest reference
if parts[0] == "isulad" {
randomID := stringid.GenerateNonCryptoID()[:constant.DefaultIDLen]
- isuladTarPath = filepath.Join(opts.DataDir, fmt.Sprintf("isula-build-tmp-%s.tar", randomID))
+ isuladTarPath, err = securejoin.SecureJoin(opts.DataDir, fmt.Sprintf("isula-build-tmp-%s.tar", randomID))
+ if err != nil {
+ return nil, "", err
+ }
// construct format: transport:path:image:tag
// parts[1] here could not be empty cause client-end already processed it
destSpec = fmt.Sprintf("docker-archive:%s:%s", isuladTarPath, parts[1])
diff --git a/store/store.go b/store/store.go
index 263d69e8..410eef11 100644
--- a/store/store.go
+++ b/store/store.go
@@ -131,15 +131,15 @@ func (s *Store) CleanContainer(id string) error {
// Do not care about all the errors whiling cleaning the container,
// just return one if the error occurs.
- var err error
+ var finalErr error
if _, uerr := s.Unmount(id, false); uerr != nil {
- err = uerr
+ finalErr = uerr
logrus.Warnf("Unmount container store failed while cleaning %q", id)
}
if derr := s.DeleteContainer(id); derr != nil {
- err = derr
+ finalErr = derr
logrus.Warnf("Delete container store failed while cleaning %q", id)
}
- return err
+ return finalErr
}
diff --git a/util/common.go b/util/common.go
index 9e2e2537..5cd4bb28 100644
--- a/util/common.go
+++ b/util/common.go
@@ -20,6 +20,7 @@ import (
"path/filepath"
"strings"
+ securejoin "github.com/cyphar/filepath-securejoin"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.org/x/sys/unix"
@@ -152,6 +153,17 @@ func ParseServer(server string) (string, error) {
return "", errors.Errorf("invalid registry address %s", server)
}
+ // to prevent directory traversal
+ fakePrefix := "/fakePrefix"
+ origAddr := fmt.Sprintf("%s/%s", fakePrefix, fields[0])
+ cleanAddr, err := securejoin.SecureJoin(fakePrefix, fields[0])
+ if err != nil {
+ return "", err
+ }
+ if cleanAddr != origAddr {
+ return "", errors.Errorf("invalid relative path detected")
+ }
+
return fields[0], nil
}
diff --git a/util/common_test.go b/util/common_test.go
index b48c508f..e9b6ad85 100644
--- a/util/common_test.go
+++ b/util/common_test.go
@@ -153,17 +153,29 @@ func TestParseServer(t *testing.T) {
want: "",
wantErr: true,
},
+ {
+ name: "TC10 - abnormal server address with relative filepath",
+ args: args{server: "https://mydockerhub/../../../"},
+ want: "mydockerhub",
+ wantErr: false,
+ },
+ {
+ name: "TC11 - abnormal server address with relative filepath 2",
+ args: args{server: "https://../../../../mydockerhub"},
+ want: "",
+ wantErr: true,
+ },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseServer(tt.args.server)
+ if got != tt.want {
+ t.Errorf("ParseServer() got = %v, want %v", got, tt.want)
+ }
if (err != nil) != tt.wantErr {
t.Errorf("ParseServer() error = %v, wantErr %v", err, tt.wantErr)
return
}
- if got != tt.want {
- t.Errorf("ParseServer() got = %v, want %v", got, tt.want)
- }
})
}
}
--
2.27.0