1
0
mirror of https://github.com/golang/go synced 2024-11-15 02:30:31 -07:00
go/src/time/zoneinfo_read.go
Cherry Mui c4772d30bf cmd/link: disallow pull-only linknames
As mentioned in CL 584598, linkname is a mechanism that, when
abused, can break API integrity and even safety of Go programs.
CL 584598 is a first step to restrict the use of linknames, by
implementing a blocklist. This CL takes a step further, tightening
up the restriction by allowing linkname references ("pull") only
when the definition side explicitly opts into it, by having a
linkname on the definition (possibly to itself). This way, it is at
least clear on the definition side that the symbol, despite being
unexported, is accessed outside of the package. Unexported symbols
without linkname can now be actually private. This is similar to
the symbol visibility rule used by gccgo for years (which defines
unexported non-linknamed symbols as C static symbols).

As there can be pull-only linknames in the wild that may be broken
by this change, we currently only enforce this rule for symbols
defined in the standard library. Push linknames are added in the
standard library to allow things build.

Linkname references to external (non-Go) symbols are still allowed,
as their visibility is controlled by the C symbol visibility rules
and enforced by the C (static or dynamic) linker.

Assembly symbols are treated similar to linknamed symbols.

This is controlled by -checklinkname linker flag, currently not
enabled by default. A follow-up CL will enable it by default.

Change-Id: I07344f5c7a02124dbbef0fbc8fec3b666a4b2b0e
Reviewed-on: https://go-review.googlesource.com/c/go/+/585358
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Than McIntosh <thanm@google.com>
Reviewed-by: Russ Cox <rsc@golang.org>
2024-05-15 19:57:43 +00:00

600 lines
14 KiB
Go

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Parse "zoneinfo" time zone file.
// This is a fairly standard file format used on OS X, Linux, BSD, Sun, and others.
// See tzfile(5), https://en.wikipedia.org/wiki/Zoneinfo,
// and ftp://munnari.oz.au/pub/oldtz/
package time
import (
"errors"
"internal/bytealg"
"runtime"
"syscall"
_ "unsafe" // for linkname
)
// registerLoadFromEmbeddedTZData is called by the time/tzdata package,
// if it is imported.
//
//go:linkname registerLoadFromEmbeddedTZData
func registerLoadFromEmbeddedTZData(f func(string) (string, error)) {
loadFromEmbeddedTZData = f
}
// loadFromEmbeddedTZData is used to load a specific tzdata file
// from tzdata information embedded in the binary itself.
// This is set when the time/tzdata package is imported,
// via registerLoadFromEmbeddedTzdata.
var loadFromEmbeddedTZData func(zipname string) (string, error)
// 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
seekCurrent = 1
seekEnd = 2
)
// Simple I/O interface to binary blob of data.
type dataIO struct {
p []byte
error bool
}
func (d *dataIO) read(n int) []byte {
if len(d.p) < n {
d.p = nil
d.error = true
return nil
}
p := d.p[0:n]
d.p = d.p[n:]
return p
}
func (d *dataIO) big4() (n uint32, ok bool) {
p := d.read(4)
if len(p) < 4 {
d.error = true
return 0, false
}
return uint32(p[3]) | uint32(p[2])<<8 | uint32(p[1])<<16 | uint32(p[0])<<24, true
}
func (d *dataIO) big8() (n uint64, ok bool) {
n1, ok1 := d.big4()
n2, ok2 := d.big4()
if !ok1 || !ok2 {
d.error = true
return 0, false
}
return (uint64(n1) << 32) | uint64(n2), true
}
func (d *dataIO) byte() (n byte, ok bool) {
p := d.read(1)
if len(p) < 1 {
d.error = true
return 0, false
}
return p[0], true
}
// rest returns the rest of the data in the buffer.
func (d *dataIO) rest() []byte {
r := d.p
d.p = nil
return r
}
// Make a string by stopping at the first NUL
func byteString(p []byte) string {
if i := bytealg.IndexByte(p, 0); i != -1 {
p = p[:i]
}
return string(p)
}
var errBadData = errors.New("malformed time zone information")
// LoadLocationFromTZData returns a Location with the given name
// initialized from the IANA Time Zone database-formatted data.
// The data should be in the format of a standard IANA time zone file
// (for example, the content of /etc/localtime on Unix systems).
func LoadLocationFromTZData(name string, data []byte) (*Location, error) {
d := dataIO{data, false}
// 4-byte magic "TZif"
if magic := d.read(4); string(magic) != "TZif" {
return nil, errBadData
}
// 1-byte version, then 15 bytes of padding
var version int
var p []byte
if p = d.read(16); len(p) != 16 {
return nil, errBadData
} else {
switch p[0] {
case 0:
version = 1
case '2':
version = 2
case '3':
version = 3
default:
return nil, errBadData
}
}
// six big-endian 32-bit integers:
// number of UTC/local indicators
// number of standard/wall indicators
// number of leap seconds
// number of transition times
// number of local time zones
// number of characters of time zone abbrev strings
const (
NUTCLocal = iota
NStdWall
NLeap
NTime
NZone
NChar
)
var n [6]int
for i := 0; i < 6; i++ {
nn, ok := d.big4()
if !ok {
return nil, errBadData
}
if uint32(int(nn)) != nn {
return nil, errBadData
}
n[i] = int(nn)
}
// If we have version 2 or 3, then the data is first written out
// in a 32-bit format, then written out again in a 64-bit format.
// Skip the 32-bit format and read the 64-bit one, as it can
// describe a broader range of dates.
is64 := false
if version > 1 {
// Skip the 32-bit data.
skip := n[NTime]*4 +
n[NTime] +
n[NZone]*6 +
n[NChar] +
n[NLeap]*8 +
n[NStdWall] +
n[NUTCLocal]
// Skip the version 2 header that we just read.
skip += 4 + 16
d.read(skip)
is64 = true
// Read the counts again, they can differ.
for i := 0; i < 6; i++ {
nn, ok := d.big4()
if !ok {
return nil, errBadData
}
if uint32(int(nn)) != nn {
return nil, errBadData
}
n[i] = int(nn)
}
}
size := 4
if is64 {
size = 8
}
// Transition times.
txtimes := dataIO{d.read(n[NTime] * size), false}
// Time zone indices for transition times.
txzones := d.read(n[NTime])
// Zone info structures
zonedata := dataIO{d.read(n[NZone] * 6), false}
// Time zone abbreviations.
abbrev := d.read(n[NChar])
// Leap-second time pairs
d.read(n[NLeap] * (size + 4))
// Whether tx times associated with local time types
// are specified as standard time or wall time.
isstd := d.read(n[NStdWall])
// Whether tx times associated with local time types
// are specified as UTC or local time.
isutc := d.read(n[NUTCLocal])
if d.error { // ran out of data
return nil, errBadData
}
var extend string
rest := d.rest()
if len(rest) > 2 && rest[0] == '\n' && rest[len(rest)-1] == '\n' {
extend = string(rest[1 : len(rest)-1])
}
// Now we can build up a useful data structure.
// First the zone information.
// utcoff[4] isdst[1] nameindex[1]
nzone := n[NZone]
if nzone == 0 {
// Reject tzdata files with no zones. There's nothing useful in them.
// This also avoids a panic later when we add and then use a fake transition (golang.org/issue/29437).
return nil, errBadData
}
zones := make([]zone, nzone)
for i := range zones {
var ok bool
var n uint32
if n, ok = zonedata.big4(); !ok {
return nil, errBadData
}
if uint32(int(n)) != n {
return nil, errBadData
}
zones[i].offset = int(int32(n))
var b byte
if b, ok = zonedata.byte(); !ok {
return nil, errBadData
}
zones[i].isDST = b != 0
if b, ok = zonedata.byte(); !ok || int(b) >= len(abbrev) {
return nil, errBadData
}
zones[i].name = byteString(abbrev[b:])
if runtime.GOOS == "aix" && len(name) > 8 && (name[:8] == "Etc/GMT+" || name[:8] == "Etc/GMT-") {
// There is a bug with AIX 7.2 TL 0 with files in Etc,
// GMT+1 will return GMT-1 instead of GMT+1 or -01.
if name != "Etc/GMT+0" {
// GMT+0 is OK
zones[i].name = name[4:]
}
}
}
// Now the transition time info.
tx := make([]zoneTrans, n[NTime])
for i := range tx {
var n int64
if !is64 {
if n4, ok := txtimes.big4(); !ok {
return nil, errBadData
} else {
n = int64(int32(n4))
}
} else {
if n8, ok := txtimes.big8(); !ok {
return nil, errBadData
} else {
n = int64(n8)
}
}
tx[i].when = n
if int(txzones[i]) >= len(zones) {
return nil, errBadData
}
tx[i].index = txzones[i]
if i < len(isstd) {
tx[i].isstd = isstd[i] != 0
}
if i < len(isutc) {
tx[i].isutc = isutc[i] != 0
}
}
if len(tx) == 0 {
// Build fake transition to cover all time.
// This happens in fixed locations like "Etc/GMT0".
tx = append(tx, zoneTrans{when: alpha, index: 0})
}
// Committed to succeed.
l := &Location{zone: zones, tx: tx, name: name, extend: extend}
// Fill in the cache with information about right now,
// since that will be the most common lookup.
sec, _, _ := now()
for i := range tx {
if tx[i].when <= sec && (i+1 == len(tx) || sec < tx[i+1].when) {
l.cacheStart = tx[i].when
l.cacheEnd = omega
l.cacheZone = &l.zone[tx[i].index]
if i+1 < len(tx) {
l.cacheEnd = tx[i+1].when
} else if l.extend != "" {
// If we're at the end of the known zone transitions,
// try the extend string.
if name, offset, estart, eend, isDST, ok := tzset(l.extend, l.cacheStart, sec); ok {
l.cacheStart = estart
l.cacheEnd = eend
// Find the zone that is returned by tzset to avoid allocation if possible.
if zoneIdx := findZone(l.zone, name, offset, isDST); zoneIdx != -1 {
l.cacheZone = &l.zone[zoneIdx]
} else {
l.cacheZone = &zone{
name: name,
offset: offset,
isDST: isDST,
}
}
}
}
break
}
}
return l, nil
}
func findZone(zones []zone, name string, offset int, isDST bool) int {
for i, z := range zones {
if z.name == name && z.offset == offset && z.isDST == isDST {
return i
}
}
return -1
}
// loadTzinfoFromDirOrZip returns the contents of the file with the given name
// in dir. dir can either be an uncompressed zip file, or a directory.
func loadTzinfoFromDirOrZip(dir, name string) ([]byte, error) {
if len(dir) > 4 && dir[len(dir)-4:] == ".zip" {
return loadTzinfoFromZip(dir, name)
}
if dir != "" {
name = dir + "/" + name
}
return readFile(name)
}
// There are 500+ zoneinfo files. Rather than distribute them all
// individually, we ship them in an uncompressed zip file.
// Used this way, the zip file format serves as a commonly readable
// container for the individual small files. We choose zip over tar
// because zip files have a contiguous table of contents, making
// individual file lookups faster, and because the per-file overhead
// in a zip file is considerably less than tar's 512 bytes.
// get4 returns the little-endian 32-bit value in b.
func get4(b []byte) int {
if len(b) < 4 {
return 0
}
return int(b[0]) | int(b[1])<<8 | int(b[2])<<16 | int(b[3])<<24
}
// get2 returns the little-endian 16-bit value in b.
func get2(b []byte) int {
if len(b) < 2 {
return 0
}
return int(b[0]) | int(b[1])<<8
}
// loadTzinfoFromZip returns the contents of the file with the given name
// in the given uncompressed zip file.
func loadTzinfoFromZip(zipfile, name string) ([]byte, error) {
fd, err := open(zipfile)
if err != nil {
return nil, err
}
defer closefd(fd)
const (
zecheader = 0x06054b50
zcheader = 0x02014b50
ztailsize = 22
zheadersize = 30
zheader = 0x04034b50
)
buf := make([]byte, ztailsize)
if err := preadn(fd, buf, -ztailsize); err != nil || get4(buf) != zecheader {
return nil, errors.New("corrupt zip file " + zipfile)
}
n := get2(buf[10:])
size := get4(buf[12:])
off := get4(buf[16:])
buf = make([]byte, size)
if err := preadn(fd, buf, off); err != nil {
return nil, errors.New("corrupt zip file " + zipfile)
}
for i := 0; i < n; i++ {
// zip entry layout:
// 0 magic[4]
// 4 madevers[1]
// 5 madeos[1]
// 6 extvers[1]
// 7 extos[1]
// 8 flags[2]
// 10 meth[2]
// 12 modtime[2]
// 14 moddate[2]
// 16 crc[4]
// 20 csize[4]
// 24 uncsize[4]
// 28 namelen[2]
// 30 xlen[2]
// 32 fclen[2]
// 34 disknum[2]
// 36 iattr[2]
// 38 eattr[4]
// 42 off[4]
// 46 name[namelen]
// 46+namelen+xlen+fclen - next header
//
if get4(buf) != zcheader {
break
}
meth := get2(buf[10:])
size := get4(buf[24:])
namelen := get2(buf[28:])
xlen := get2(buf[30:])
fclen := get2(buf[32:])
off := get4(buf[42:])
zname := buf[46 : 46+namelen]
buf = buf[46+namelen+xlen+fclen:]
if string(zname) != name {
continue
}
if meth != 0 {
return nil, errors.New("unsupported compression for " + name + " in " + zipfile)
}
// zip per-file header layout:
// 0 magic[4]
// 4 extvers[1]
// 5 extos[1]
// 6 flags[2]
// 8 meth[2]
// 10 modtime[2]
// 12 moddate[2]
// 14 crc[4]
// 18 csize[4]
// 22 uncsize[4]
// 26 namelen[2]
// 28 xlen[2]
// 30 name[namelen]
// 30+namelen+xlen - file data
//
buf = make([]byte, zheadersize+namelen)
if err := preadn(fd, buf, off); err != nil ||
get4(buf) != zheader ||
get2(buf[8:]) != meth ||
get2(buf[26:]) != namelen ||
string(buf[30:30+namelen]) != name {
return nil, errors.New("corrupt zip file " + zipfile)
}
xlen = get2(buf[28:])
buf = make([]byte, size)
if err := preadn(fd, buf, off+30+namelen+xlen); err != nil {
return nil, errors.New("corrupt zip file " + zipfile)
}
return buf, nil
}
return nil, syscall.ENOENT
}
// loadTzinfoFromTzdata returns the time zone information of the time zone
// with the given name, from a tzdata database file as they are typically
// found on android.
var loadTzinfoFromTzdata func(file, name string) ([]byte, error)
// loadTzinfo returns the time zone information of the time zone
// with the given name, from a given source. A source may be a
// timezone database directory, tzdata database file or an uncompressed
// zip file, containing the contents of such a directory.
func loadTzinfo(name string, source string) ([]byte, error) {
if len(source) >= 6 && source[len(source)-6:] == "tzdata" {
return loadTzinfoFromTzdata(source, name)
}
return loadTzinfoFromDirOrZip(source, name)
}
// loadLocation returns the Location with the given name from one of
// the specified sources. See loadTzinfo for a list of supported sources.
// The first timezone data matching the given name that is successfully loaded
// and parsed is returned as a Location.
func loadLocation(name string, sources []string) (z *Location, firstErr error) {
for _, source := range sources {
zoneData, err := loadTzinfo(name, source)
if err == nil {
if z, err = LoadLocationFromTZData(name, zoneData); err == nil {
return z, nil
}
}
if firstErr == nil && err != syscall.ENOENT {
firstErr = err
}
}
if loadFromEmbeddedTZData != nil {
zoneData, err := loadFromEmbeddedTZData(name)
if err == nil {
if z, err = LoadLocationFromTZData(name, []byte(zoneData)); err == nil {
return z, nil
}
}
if firstErr == nil && err != syscall.ENOENT {
firstErr = err
}
}
if source, ok := gorootZoneSource(runtime.GOROOT()); ok {
zoneData, err := loadTzinfo(name, source)
if err == nil {
if z, err = LoadLocationFromTZData(name, zoneData); err == nil {
return z, nil
}
}
if firstErr == nil && err != syscall.ENOENT {
firstErr = err
}
}
if firstErr != nil {
return nil, firstErr
}
return nil, errors.New("unknown time zone " + name)
}
// readFile reads and returns the content of the named file.
// It is a trivial implementation of os.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 := open(name)
if err != nil {
return nil, err
}
defer closefd(f)
var (
buf [4096]byte
ret []byte
n int
)
for {
n, err = read(f, buf[:])
if n > 0 {
ret = append(ret, buf[:n]...)
}
if n == 0 || err != nil {
break
}
if len(ret) > maxFileSize {
return nil, fileSizeError(name)
}
}
return ret, err
}