Commit f5f7d6e3 authored by Damien Neil's avatar Damien Neil

syscall: validate ParseDirent inputs

Don't panic, crash, or return references to uninitialized memory when
ParseDirent is passed invalid input.

Move common dirent parsing to syscall.go with minimal platform-specific
functions in syscall_$GOOS.go.

Fixes #15653

Change-Id: I5602475e02321fe381064488401c14b33bec6886
Reviewed-on: https://go-review.googlesource.com/23780
Run-TryBot: Damien Neil <dneil@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarBrad Fitzpatrick <bradfitz@golang.org>
parent ab592357
// 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.
// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris
package syscall
import "unsafe"
// readInt returns the size-bytes unsigned integer in native byte order at offset off.
func readInt(b []byte, off, size uintptr) (u uint64, ok bool) {
if len(b) < int(off+size) {
return 0, false
}
if isBigEndian {
return readIntBE(b[off:], size), true
}
return readIntLE(b[off:], size), true
}
func readIntBE(b []byte, size uintptr) uint64 {
switch size {
case 1:
return uint64(b[0])
case 2:
_ = b[1] // bounds check hint to compiler; see golang.org/issue/14808
return uint64(b[1]) | uint64(b[0])<<8
case 4:
_ = b[3] // bounds check hint to compiler; see golang.org/issue/14808
return uint64(b[3]) | uint64(b[2])<<8 | uint64(b[1])<<16 | uint64(b[0])<<24
case 8:
_ = b[7] // bounds check hint to compiler; see golang.org/issue/14808
return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 |
uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56
default:
panic("syscall: readInt with unsupported size")
}
}
func readIntLE(b []byte, size uintptr) uint64 {
switch size {
case 1:
return uint64(b[0])
case 2:
_ = b[1] // bounds check hint to compiler; see golang.org/issue/14808
return uint64(b[0]) | uint64(b[1])<<8
case 4:
_ = b[3] // bounds check hint to compiler; see golang.org/issue/14808
return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24
case 8:
_ = b[7] // bounds check hint to compiler; see golang.org/issue/14808
return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 |
uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56
default:
panic("syscall: readInt with unsupported size")
}
}
// ParseDirent parses up to max directory entries in buf,
// appending the names to names. It returns the number of
// bytes consumed from buf, the number of entries added
// to names, and the new names slice.
func ParseDirent(buf []byte, max int, names []string) (consumed int, count int, newnames []string) {
origlen := len(buf)
count = 0
for max != 0 && len(buf) > 0 {
reclen, ok := direntReclen(buf)
if !ok || reclen > uint64(len(buf)) {
return origlen, count, names
}
rec := buf[:reclen]
buf = buf[reclen:]
ino, ok := direntIno(rec)
if !ok {
break
}
if ino == 0 { // File absent in directory.
continue
}
const namoff = uint64(unsafe.Offsetof(Dirent{}.Name))
namlen, ok := direntNamlen(rec)
if !ok || namoff+namlen > uint64(len(rec)) {
break
}
name := rec[namoff : namoff+namlen]
for i, c := range name {
if c == 0 {
name = name[:i]
break
}
}
// Check for useless names before allocating a string.
if string(name) == "." || string(name) == ".." {
continue
}
max--
count++
names = append(names, string(name))
}
return origlen - len(buf), count, names
}
// Copyright 2016 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.
//
// +build ppc64 s390x mips64
package syscall
const isBigEndian = true
// Copyright 2016 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.
//
// +build 386 amd64 amd64p32 arm arm64 ppc64le mips64le
package syscall
const isBigEndian = false
...@@ -75,32 +75,16 @@ func nametomib(name string) (mib []_C_int, err error) { ...@@ -75,32 +75,16 @@ func nametomib(name string) (mib []_C_int, err error) {
return buf[0 : n/siz], nil return buf[0 : n/siz], nil
} }
// ParseDirent parses up to max directory entries in buf, func direntIno(buf []byte) (uint64, bool) {
// appending the names to names. It returns the number return readInt(buf, unsafe.Offsetof(Dirent{}.Ino), unsafe.Sizeof(Dirent{}.Ino))
// bytes consumed from buf, the number of entries added }
// to names, and the new names slice.
func ParseDirent(buf []byte, max int, names []string) (consumed int, count int, newnames []string) { func direntReclen(buf []byte) (uint64, bool) {
origlen := len(buf) return readInt(buf, unsafe.Offsetof(Dirent{}.Reclen), unsafe.Sizeof(Dirent{}.Reclen))
for max != 0 && len(buf) > 0 { }
dirent := (*Dirent)(unsafe.Pointer(&buf[0]))
if dirent.Reclen == 0 { func direntNamlen(buf []byte) (uint64, bool) {
buf = nil return readInt(buf, unsafe.Offsetof(Dirent{}.Namlen), unsafe.Sizeof(Dirent{}.Namlen))
break
}
buf = buf[dirent.Reclen:]
if dirent.Ino == 0 { // File absent in directory.
continue
}
bytes := (*[10000]byte)(unsafe.Pointer(&dirent.Name[0]))
var name = string(bytes[0:dirent.Namlen])
if name == "." || name == ".." { // Useless names
continue
}
max--
count++
names = append(names, name)
}
return origlen - len(buf), count, names
} }
//sys ptrace(request int, pid int, addr uintptr, data uintptr) (err error) //sys ptrace(request int, pid int, addr uintptr, data uintptr) (err error)
......
...@@ -56,29 +56,20 @@ func nametomib(name string) (mib []_C_int, err error) { ...@@ -56,29 +56,20 @@ func nametomib(name string) (mib []_C_int, err error) {
return buf[0 : n/siz], nil return buf[0 : n/siz], nil
} }
// ParseDirent parses up to max directory entries in buf, func direntIno(buf []byte) (uint64, bool) {
// appending the names to names. It returns the number return readInt(buf, unsafe.Offsetof(Dirent{}.Ino), unsafe.Sizeof(Dirent{}.Ino))
// bytes consumed from buf, the number of entries added }
// to names, and the new names slice.
func ParseDirent(buf []byte, max int, names []string) (consumed int, count int, newnames []string) { func direntReclen(buf []byte) (uint64, bool) {
origlen := len(buf) namlen, ok := direntNamlen(buf)
for max != 0 && len(buf) > 0 { if !ok {
dirent := (*Dirent)(unsafe.Pointer(&buf[0])) return 0, false
reclen := int(16+dirent.Namlen+1+7) & ^7
buf = buf[reclen:]
if dirent.Fileno == 0 { // File absent in directory.
continue
}
bytes := (*[10000]byte)(unsafe.Pointer(&dirent.Name[0]))
var name = string(bytes[0:dirent.Namlen])
if name == "." || name == ".." { // Useless names
continue
}
max--
count++
names = append(names, name)
} }
return origlen - len(buf), count, names return (16 + namlen + 1 + 7) & ^7, true
}
func direntNamlen(buf []byte) (uint64, bool) {
return readInt(buf, unsafe.Offsetof(Dirent{}.Namlen), unsafe.Sizeof(Dirent{}.Namlen))
} }
//sysnb pipe() (r int, w int, err error) //sysnb pipe() (r int, w int, err error)
......
...@@ -54,32 +54,16 @@ func nametomib(name string) (mib []_C_int, err error) { ...@@ -54,32 +54,16 @@ func nametomib(name string) (mib []_C_int, err error) {
return buf[0 : n/siz], nil return buf[0 : n/siz], nil
} }
// ParseDirent parses up to max directory entries in buf, func direntIno(buf []byte) (uint64, bool) {
// appending the names to names. It returns the number return readInt(buf, unsafe.Offsetof(Dirent{}.Fileno), unsafe.Sizeof(Dirent{}.Fileno))
// bytes consumed from buf, the number of entries added }
// to names, and the new names slice.
func ParseDirent(buf []byte, max int, names []string) (consumed int, count int, newnames []string) { func direntReclen(buf []byte) (uint64, bool) {
origlen := len(buf) return readInt(buf, unsafe.Offsetof(Dirent{}.Reclen), unsafe.Sizeof(Dirent{}.Reclen))
for max != 0 && len(buf) > 0 { }
dirent := (*Dirent)(unsafe.Pointer(&buf[0]))
if dirent.Reclen == 0 { func direntNamlen(buf []byte) (uint64, bool) {
buf = nil return readInt(buf, unsafe.Offsetof(Dirent{}.Namlen), unsafe.Sizeof(Dirent{}.Namlen))
break
}
buf = buf[dirent.Reclen:]
if dirent.Fileno == 0 { // File absent in directory.
continue
}
bytes := (*[10000]byte)(unsafe.Pointer(&dirent.Name[0]))
var name = string(bytes[0:dirent.Namlen])
if name == "." || name == ".." { // Useless names
continue
}
max--
count++
names = append(names, name)
}
return origlen - len(buf), count, names
} }
//sysnb pipe() (r int, w int, err error) //sysnb pipe() (r int, w int, err error)
......
...@@ -757,38 +757,24 @@ func Reboot(cmd int) (err error) { ...@@ -757,38 +757,24 @@ func Reboot(cmd int) (err error) {
return reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, cmd, "") return reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, cmd, "")
} }
func clen(n []byte) int {
for i := 0; i < len(n); i++ {
if n[i] == 0 {
return i
}
}
return len(n)
}
func ReadDirent(fd int, buf []byte) (n int, err error) { func ReadDirent(fd int, buf []byte) (n int, err error) {
return Getdents(fd, buf) return Getdents(fd, buf)
} }
func ParseDirent(buf []byte, max int, names []string) (consumed int, count int, newnames []string) { func direntIno(buf []byte) (uint64, bool) {
origlen := len(buf) return readInt(buf, unsafe.Offsetof(Dirent{}.Ino), unsafe.Sizeof(Dirent{}.Ino))
count = 0 }
for max != 0 && len(buf) > 0 {
dirent := (*Dirent)(unsafe.Pointer(&buf[0])) func direntReclen(buf []byte) (uint64, bool) {
buf = buf[dirent.Reclen:] return readInt(buf, unsafe.Offsetof(Dirent{}.Reclen), unsafe.Sizeof(Dirent{}.Reclen))
if dirent.Ino == 0 { // File absent in directory. }
continue
} func direntNamlen(buf []byte) (uint64, bool) {
bytes := (*[10000]byte)(unsafe.Pointer(&dirent.Name[0])) reclen, ok := direntReclen(buf)
var name = string(bytes[0:clen(bytes[:])]) if !ok {
if name == "." || name == ".." { // Useless names return 0, false
continue
}
max--
count++
names = append(names, name)
} }
return origlen - len(buf), count, names return reclen - uint64(unsafe.Offsetof(Dirent{}.Name)), true
} }
//sys mount(source string, target string, fstype string, flags uintptr, data *byte) (err error) //sys mount(source string, target string, fstype string, flags uintptr, data *byte) (err error)
......
...@@ -26,34 +26,20 @@ type Dirent struct { ...@@ -26,34 +26,20 @@ type Dirent struct {
Name [256]byte Name [256]byte
} }
func ParseDirent(buf []byte, max int, names []string) (consumed int, count int, newnames []string) { func direntIno(buf []byte) (uint64, bool) {
origlen := len(buf) return readInt(buf, unsafe.Offsetof(Dirent{}.Ino), unsafe.Sizeof(Dirent{}.Ino))
count = 0
for max != 0 && len(buf) > 0 {
dirent := (*Dirent)(unsafe.Pointer(&buf[0]))
buf = buf[dirent.Reclen:]
if dirent.Ino == 0 { // File absent in directory.
continue
}
bytes := (*[512 + PathMax]byte)(unsafe.Pointer(&dirent.Name[0]))
var name = string(bytes[0:clen(bytes[:])])
if name == "." || name == ".." { // Useless names
continue
}
max--
count++
names = append(names, name)
}
return origlen - len(buf), count, names
} }
func clen(n []byte) int { func direntReclen(buf []byte) (uint64, bool) {
for i := 0; i < len(n); i++ { return readInt(buf, unsafe.Offsetof(Dirent{}.Reclen), unsafe.Sizeof(Dirent{}.Reclen))
if n[i] == 0 { }
return i
} func direntNamlen(buf []byte) (uint64, bool) {
reclen, ok := direntReclen(buf)
if !ok {
return 0, false
} }
return len(n) return reclen - uint64(unsafe.Offsetof(Dirent{}.Name)), true
} }
const PathMax = 256 const PathMax = 256
......
...@@ -90,32 +90,16 @@ func nametomib(name string) (mib []_C_int, err error) { ...@@ -90,32 +90,16 @@ func nametomib(name string) (mib []_C_int, err error) {
return mib, nil return mib, nil
} }
// ParseDirent parses up to max directory entries in buf, func direntIno(buf []byte) (uint64, bool) {
// appending the names to names. It returns the number return readInt(buf, unsafe.Offsetof(Dirent{}.Fileno), unsafe.Sizeof(Dirent{}.Fileno))
// bytes consumed from buf, the number of entries added }
// to names, and the new names slice.
func ParseDirent(buf []byte, max int, names []string) (consumed int, count int, newnames []string) { func direntReclen(buf []byte) (uint64, bool) {
origlen := len(buf) return readInt(buf, unsafe.Offsetof(Dirent{}.Reclen), unsafe.Sizeof(Dirent{}.Reclen))
for max != 0 && len(buf) > 0 { }
dirent := (*Dirent)(unsafe.Pointer(&buf[0]))
if dirent.Reclen == 0 { func direntNamlen(buf []byte) (uint64, bool) {
buf = nil return readInt(buf, unsafe.Offsetof(Dirent{}.Namlen), unsafe.Sizeof(Dirent{}.Namlen))
break
}
buf = buf[dirent.Reclen:]
if dirent.Fileno == 0 { // File absent in directory.
continue
}
bytes := (*[10000]byte)(unsafe.Pointer(&dirent.Name[0]))
var name = string(bytes[0:dirent.Namlen])
if name == "." || name == ".." { // Useless names
continue
}
max--
count++
names = append(names, name)
}
return origlen - len(buf), count, names
} }
//sysnb pipe() (fd1 int, fd2 int, err error) //sysnb pipe() (fd1 int, fd2 int, err error)
......
...@@ -50,32 +50,16 @@ func nametomib(name string) (mib []_C_int, err error) { ...@@ -50,32 +50,16 @@ func nametomib(name string) (mib []_C_int, err error) {
return nil, EINVAL return nil, EINVAL
} }
// ParseDirent parses up to max directory entries in buf, func direntIno(buf []byte) (uint64, bool) {
// appending the names to names. It returns the number return readInt(buf, unsafe.Offsetof(Dirent{}.Fileno), unsafe.Sizeof(Dirent{}.Fileno))
// bytes consumed from buf, the number of entries added }
// to names, and the new names slice.
func ParseDirent(buf []byte, max int, names []string) (consumed int, count int, newnames []string) { func direntReclen(buf []byte) (uint64, bool) {
origlen := len(buf) return readInt(buf, unsafe.Offsetof(Dirent{}.Reclen), unsafe.Sizeof(Dirent{}.Reclen))
for max != 0 && len(buf) > 0 { }
dirent := (*Dirent)(unsafe.Pointer(&buf[0]))
if dirent.Reclen == 0 { func direntNamlen(buf []byte) (uint64, bool) {
buf = nil return readInt(buf, unsafe.Offsetof(Dirent{}.Namlen), unsafe.Sizeof(Dirent{}.Namlen))
break
}
buf = buf[dirent.Reclen:]
if dirent.Fileno == 0 { // File absent in directory.
continue
}
bytes := (*[10000]byte)(unsafe.Pointer(&dirent.Name[0]))
var name = string(bytes[0:dirent.Namlen])
if name == "." || name == ".." { // Useless names
continue
}
max--
count++
names = append(names, name)
}
return origlen - len(buf), count, names
} }
//sysnb pipe(p *[2]_C_int) (err error) //sysnb pipe(p *[2]_C_int) (err error)
......
...@@ -38,32 +38,20 @@ func clen(n []byte) int { ...@@ -38,32 +38,20 @@ func clen(n []byte) int {
return len(n) return len(n)
} }
// ParseDirent parses up to max directory entries in buf, func direntIno(buf []byte) (uint64, bool) {
// appending the names to names. It returns the number return readInt(buf, unsafe.Offsetof(Dirent{}.Ino), unsafe.Sizeof(Dirent{}.Ino))
// bytes consumed from buf, the number of entries added }
// to names, and the new names slice.
func ParseDirent(buf []byte, max int, names []string) (consumed int, count int, newnames []string) { func direntReclen(buf []byte) (uint64, bool) {
origlen := len(buf) return readInt(buf, unsafe.Offsetof(Dirent{}.Reclen), unsafe.Sizeof(Dirent{}.Reclen))
for max != 0 && len(buf) > 0 { }
dirent := (*Dirent)(unsafe.Pointer(&buf[0]))
if dirent.Reclen == 0 { func direntNamlen(buf []byte) (uint64, bool) {
buf = nil reclen, ok := direntReclen(buf)
break if !ok {
} return 0, false
buf = buf[dirent.Reclen:]
if dirent.Ino == 0 { // File absent in directory.
continue
}
bytes := (*[10000]byte)(unsafe.Pointer(&dirent.Name[0]))
var name = string(bytes[0:clen(bytes[:])])
if name == "." || name == ".." { // Useless names
continue
}
max--
count++
names = append(names, name)
} }
return origlen - len(buf), count, names return reclen - uint64(unsafe.Offsetof(Dirent{}.Name)), true
} }
func pipe() (r uintptr, w uintptr, err uintptr) func pipe() (r uintptr, w uintptr, err uintptr)
......
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