Commit bd561699 authored by Brad Fitzpatrick's avatar Brad Fitzpatrick

time: bound file reads and validate LoadLocation argument

Fixes #18985

Change-Id: I956117f47d1d2b453b4786c7b78c1c944defeca0
Reviewed-on: https://go-review.googlesource.com/36551Reviewed-by: default avatarIan Lance Taylor <iant@golang.org>
parent e410d2a8
......@@ -32,4 +32,6 @@ var (
ParseTimeZone = parseTimeZone
SetMono = (*Time).setMono
GetMono = (*Time).mono
ErrLocation = errLocation
ReadFile = readFile
)
......@@ -19,6 +19,7 @@ func interrupt() {
// readFile reads and returns the content of the named file.
// It is a trivial implementation of ioutil.ReadFile, reimplemented
// here to avoid depending on io/ioutil or os.
// It returns an error if name exceeds maxFileSize bytes.
func readFile(name string) ([]byte, error) {
f, err := syscall.Open(name, syscall.O_RDONLY)
if err != nil {
......@@ -38,6 +39,9 @@ func readFile(name string) ([]byte, error) {
if n == 0 || err != nil {
break
}
if len(ret) > maxFileSize {
return nil, fileSizeError(name)
}
}
return ret, err
}
......
......@@ -19,6 +19,7 @@ func interrupt() {
// readFile reads and returns the content of the named file.
// It is a trivial implementation of ioutil.ReadFile, reimplemented
// here to avoid depending on io/ioutil or os.
// It returns an error if name exceeds maxFileSize bytes.
func readFile(name string) ([]byte, error) {
f, err := syscall.Open(name, syscall.O_RDONLY, 0)
if err != nil {
......@@ -38,6 +39,9 @@ func readFile(name string) ([]byte, error) {
if n == 0 || err != nil {
break
}
if len(ret) > maxFileSize {
return nil, fileSizeError(name)
}
}
return ret, err
}
......
......@@ -16,6 +16,7 @@ func interrupt() {
// readFile reads and returns the content of the named file.
// It is a trivial implementation of ioutil.ReadFile, reimplemented
// here to avoid depending on io/ioutil or os.
// It returns an error if name exceeds maxFileSize bytes.
func readFile(name string) ([]byte, error) {
f, err := syscall.Open(name, syscall.O_RDONLY, 0)
if err != nil {
......@@ -35,6 +36,9 @@ func readFile(name string) ([]byte, error) {
if n == 0 || err != nil {
break
}
if len(ret) > maxFileSize {
return nil, fileSizeError(name)
}
}
return ret, err
}
......
......@@ -11,7 +11,9 @@ import (
"fmt"
"math/big"
"math/rand"
"os"
"runtime"
"strings"
"testing"
"testing/quick"
. "time"
......@@ -1254,3 +1256,14 @@ func TestZeroMonthString(t *testing.T) {
t.Errorf("zero month = %q; want %q", got, want)
}
}
func TestReadFileLimit(t *testing.T) {
const zero = "/dev/zero"
if _, err := os.Stat(zero); err != nil {
t.Skip("skipping test without a /dev/zero")
}
_, err := ReadFile(zero)
if err == nil || !strings.Contains(err.Error(), "is too large") {
t.Errorf("readFile(%q) error = %v; want error containing 'is too large'", zero, err)
}
}
......@@ -5,6 +5,7 @@
package time
import (
"errors"
"sync"
"syscall"
)
......@@ -256,6 +257,8 @@ func (l *Location) lookupName(name string, unix int64) (offset int, isDST bool,
// NOTE(rsc): Eventually we will need to accept the POSIX TZ environment
// syntax too, but I don't feel like implementing it today.
var errLocation = errors.New("time: invalid location name")
var zoneinfo *string
var zoneinfoOnce sync.Once
......@@ -280,6 +283,11 @@ func LoadLocation(name string) (*Location, error) {
if name == "Local" {
return Local, nil
}
if containsDotDot(name) || name[0] == '/' || name[0] == '\\' {
// No valid IANA Time Zone name contains a single dot,
// much less dot dot. Likewise, none begin with a slash.
return nil, errLocation
}
zoneinfoOnce.Do(func() {
env, _ := syscall.Getenv("ZONEINFO")
zoneinfo = &env
......@@ -292,3 +300,16 @@ func LoadLocation(name string) (*Location, error) {
}
return loadLocation(name)
}
// containsDotDot reports whether s contains "..".
func containsDotDot(s string) bool {
if len(s) < 2 {
return false
}
for i := 0; i < len(s)-1; i++ {
if s[i] == '.' && s[i+1] == '.' {
return true
}
}
return false
}
......@@ -11,6 +11,17 @@ package time
import "errors"
// maxFileSize is the max permitted size of files read by readFile.
// As reference, the zoneinfo.zip distributed by Go is ~350 KB,
// so 10MB is overkill.
const maxFileSize = 10 << 20
type fileSizeError string
func (f fileSizeError) Error() string {
return "time: file " + string(f) + " is too large"
}
// Copies of io.Seek* constants to avoid importing "io":
const (
seekStart = 0
......
......@@ -20,8 +20,8 @@ func init() {
func TestEnvVarUsage(t *testing.T) {
time.ResetZoneinfoForTesting()
testZoneinfo := "foo.zip"
env := "ZONEINFO"
const testZoneinfo = "foo.zip"
const env = "ZONEINFO"
defer os.Setenv(env, os.Getenv(env))
os.Setenv(env, testZoneinfo)
......@@ -35,6 +35,26 @@ func TestEnvVarUsage(t *testing.T) {
}
}
func TestLoadLocationValidatesNames(t *testing.T) {
time.ResetZoneinfoForTesting()
const env = "ZONEINFO"
defer os.Setenv(env, os.Getenv(env))
os.Setenv(env, "")
bad := []string{
"/usr/foo/Foo",
"\\UNC\foo",
"..",
"a..",
}
for _, v := range bad {
_, err := time.LoadLocation(v)
if err != time.ErrLocation {
t.Errorf("LoadLocation(%q) error = %v; want ErrLocation", v, err)
}
}
}
func TestVersion3(t *testing.T) {
time.ForceZipFileForTesting(true)
defer time.ForceZipFileForTesting(false)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment