local: fix btime escaping the root via a planted symlink GHSA-f8g7-2xjc-7mfh CVE-PENDING
The birth-time (btime) write in writeMetadataToFile followed symlinks for any object that was not a translated link, so under -l/--links a symlink planted by an untrusted source at the destination path could redirect the btime write to a target outside the backup destination on OSes where birth time is settable (Windows). Use the NOFOLLOW birth-time write whenever translating symlinks, not only for translated links. It is a no-op on a real file or directory and stops a planted symlink from being followed out of the destination.
This commit is contained in:
@@ -447,6 +447,42 @@ func TestDirMetadataInTreeWorks(t *testing.T) {
|
||||
require.True(t, fi.ModTime().Equal(fstest.Time("2001-02-03T04:05:06Z")))
|
||||
}
|
||||
|
||||
// TestDirBTimeThroughPlantedSymlinkBlocked checks a btime write
|
||||
// through a symlink can't escape the root.
|
||||
func TestDirBTimeThroughPlantedSymlinkBlocked(t *testing.T) {
|
||||
if !haveSetBTime {
|
||||
t.Skip("birth time is not settable on this OS")
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
||||
evil := t.TempDir()
|
||||
evilDir := filepath.Join(evil, "secret.d")
|
||||
require.NoError(t, os.Mkdir(evilDir, 0700))
|
||||
|
||||
// Read the outside dir's btime through a local Fs rooted at evil.
|
||||
evilFsRaw, err := NewFs(ctx, "local", evil, configmap.Simple{})
|
||||
require.NoError(t, err)
|
||||
evilFs := evilFsRaw.(*Fs)
|
||||
readBTime := func() string {
|
||||
o, err := evilFs.newObject("secret.d")
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, o.lstat())
|
||||
m, err := o.Metadata(ctx)
|
||||
require.NoError(t, err)
|
||||
return m["btime"]
|
||||
}
|
||||
before := readBTime()
|
||||
|
||||
r := fstest.NewRun(t)
|
||||
f := r.Flocal.(*Fs)
|
||||
linksMode(f)
|
||||
require.NoError(t, putLink(ctx, f, "pwn", evilDir))
|
||||
|
||||
_, _ = f.MkdirMetadata(ctx, "pwn", fs.Metadata{"btime": "2001-02-03T04:05:06Z"})
|
||||
|
||||
require.Equal(t, before, readBTime(), "btime escaped through planted symlink to %q", evilDir)
|
||||
}
|
||||
|
||||
// TestEncodingEscapeBlocked checks that a name from a malicious source can't
|
||||
// be decoded into path syntax which writes outside the destination.
|
||||
func TestEncodingEscapeBlocked(t *testing.T) {
|
||||
|
||||
@@ -105,7 +105,10 @@ func (o *Object) writeMetadataToFile(m fs.Metadata) (outErr error) {
|
||||
}
|
||||
if haveSetBTime {
|
||||
if btimeOK {
|
||||
if o.translatedLink {
|
||||
// When translating symlinks, never follow the path. A planted symlink must
|
||||
// not redirect the birth-time write out of the root. The NOFOLLOW open is a
|
||||
// no-op on a real file or directory
|
||||
if o.translatedLink || o.fs.opt.TranslateSymlinks {
|
||||
err = lsetBTime(o.path, btime)
|
||||
} else {
|
||||
err = setBTime(o.path, btime)
|
||||
|
||||
Reference in New Issue
Block a user