mirror of
https://github.com/lima-vm/lima.git
synced 2026-10-28 11:44:35 +08:00
don't follow symlinks when writing copyToHost destination
Signed-off-by: nashit hayyat <nashit@bugqore.com>
This commit is contained in:
1 parent
549afab3f3
commit
a32fa6a2c9
3 files changed
+66
-1
No files matched your search
@@ -1316,7 +1316,9 @@ func copyToHost(ctx context.Context, sshConfig *ssh.SSHConfig, sshAddress string
|
||||
return fmt.Errorf("failed to run %v: %#q: %w", cmd.Args, string(out), err)
|
||||
}
|
||||
}
|
||||
if err := os.WriteFile(local, out, 0o600); err != nil {
|
||||
// The destination may sit inside a writable mount, so don't follow a
|
||||
// symlink planted there by the guest.
|
||||
if err := osutil.WriteFileBeneathDir(local, out, 0o600); err != nil {
|
||||
return fmt.Errorf("can't write to local file %#q: %w", local, err)
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -6,6 +6,7 @@ package osutil
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// FileExists reports whether path exists and is accessible.
|
||||
@@ -23,3 +24,27 @@ func Touch(path string) error {
|
||||
}
|
||||
return f.Close()
|
||||
}
|
||||
|
||||
// WriteFileBeneathDir writes data to name without following a symlink at the
|
||||
// final path component. It is meant for destinations that untrusted code may be
|
||||
// able to write to, e.g. a path inside a writable mount: a symlink planted at
|
||||
// name (say pointing at ~/.ssh/authorized_keys) would otherwise get the bytes
|
||||
// written through the link to a file outside the intended directory.
|
||||
// os.OpenRoot confines the write to name's parent directory, so a symlink whose
|
||||
// target escapes that directory is refused.
|
||||
func WriteFileBeneathDir(name string, data []byte, perm os.FileMode) error {
|
||||
root, err := os.OpenRoot(filepath.Dir(name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer root.Close()
|
||||
f, err := root.OpenFile(filepath.Base(name), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := f.Write(data); err != nil {
|
||||
_ = f.Close()
|
||||
return err
|
||||
}
|
||||
return f.Close()
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// SPDX-FileCopyrightText: Copyright The Lima Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package osutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestWriteFileBeneathDir(t *testing.T) {
|
||||
base := t.TempDir()
|
||||
dir := filepath.Join(base, "mnt")
|
||||
assert.NilError(t, os.MkdirAll(dir, 0o700))
|
||||
|
||||
// A regular destination is written normally.
|
||||
regular := filepath.Join(dir, "config")
|
||||
assert.NilError(t, WriteFileBeneathDir(regular, []byte("ok\n"), 0o600))
|
||||
got, err := os.ReadFile(regular)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, string(got), "ok\n")
|
||||
|
||||
// A symlink at the destination pointing outside the parent directory is
|
||||
// refused, and the link target is left untouched.
|
||||
secret := filepath.Join(base, "authorized_keys")
|
||||
assert.NilError(t, os.WriteFile(secret, []byte("ORIGINAL\n"), 0o600))
|
||||
linked := filepath.Join(dir, "linked")
|
||||
assert.NilError(t, os.Symlink(secret, linked))
|
||||
|
||||
err = WriteFileBeneathDir(linked, []byte("ATTACKER\n"), 0o600)
|
||||
assert.Assert(t, err != nil, "expected the symlinked write to be refused")
|
||||
got, err = os.ReadFile(secret)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, string(got), "ORIGINAL\n")
|
||||
}
|
||||
Reference in new issue
Block a user