英文:
Slicing operation causing buffer overrun and leaking user passwords?
问题
我有一个服务器,它有一个函数用于返回用户的注册日期。任何用户都可以查看任何非隐藏用户(在这个例子中只有user2是隐藏的)。服务器从客户端获取一个dateRequest
,并使用其中的id
来在用户文件中找到相应用户的日期:
package main
import (
"bytes"
"fmt"
)
const datelen = 10
//format: `name 0xfe date age 0xfd password`; each user is seperated by 0xff
var userfile = []byte("admin\xfe2014-01-0140\xfdadminpassword\xffuser1\xfe2014-03-0423\xfduser1password\xffuser2\xfe2014-09-2736\xfduser2password")
func main() {
c := clientInfo{0, 0, 0}
fmt.Println(string(getDate(dateRequest{c, user{0, "admin"}})))
fmt.Println(string(getDate(dateRequest{c, user{0, "admin______________"}})))
fmt.Println(string(getDate(dateRequest{c, user{1, "user1"}})))
//fmt.Println(string(getDate(dateRequest{c,user{2,"user2"}}))) // panic
fmt.Println(string(getDate(dateRequest{c, user{1, "user1_________________________________________________"}})))
}
func getDate(r dateRequest) []byte {
if r.id == 2 {
panic("hidden user")
}
user := bytes.Split(userfile, []byte{0xff})[r.id]
publicSection := bytes.Split(user, []byte{0xfd})[0]
return publicSection[len(r.username)+1 : len(r.username)+1+datelen]
}
type dateRequest struct {
clientInfo
user
}
type clientInfo struct {
reqTime uint64
ip uint32
ver uint32
}
type user struct {
id int
username string
}
如你所见,它可以正确地接收用户的日期,但是如果请求的用户名有额外的字节,部分用户的密码会被返回。不仅如此,如果你继续添加更多的字节,它会返回user2的数据,而user2应该是隐藏的。为什么会这样呢?
当执行main
的第三行代码时,getDate
中的user
和publicSection
分别是"admin�2014-01-0140�adminpassword"和"admin�2014-01-0140"。但是它返回了"dminpasswo"。为什么切片publicSection
("admin�2014-01-0140")返回了"dminpasswo"呢?这看起来像是一个缓冲区溢出的问题,但是这不应该发生,因为Go是内存安全的。我甚至尝试通过打印publicSection[len(publicSection)]
来读取超出publicSection
缓冲区的部分,但是它像预期的那样引发了panic。
我还尝试将所有的[]byte
替换为string
,出奇地解决了这个问题。
英文:
I have a server that has a function to return the registration date of a user. Any user can see any non-hidden user (in this example only user2 is hidden). The server gets a dateRequest
from a client and uses id
from that to find the corresponding user's date in the user file:
package main
import (
"bytes"
"fmt"
)
const datelen = 10
//format: `name 0xfe date age 0xfd password`; each user is seperated by 0xff
var userfile = []byte("admin\xfe2014-01-0140\xfdadminpassword\xffuser1\xfe2014-03-0423\xfduser1password\xffuser2\xfe2014-09-2736\xfduser2password")
func main() {
c := clientInfo{0, 0, 0}
fmt.Println(string(getDate(dateRequest{c, user{0, "admin"}})))
fmt.Println(string(getDate(dateRequest{c, user{0, "admin______________"}})))
fmt.Println(string(getDate(dateRequest{c, user{1, "user1"}})))
//fmt.Println(string(getDate(dateRequest{c,user{2,"user2"}}))) // panic
fmt.Println(string(getDate(dateRequest{c, user{1, "user1_________________________________________________"}})))
}
func getDate(r dateRequest) []byte {
if r.id == 2 {
panic("hidden user")
}
user := bytes.Split(userfile, []byte{0xff})[r.id]
publicSection := bytes.Split(user, []byte{0xfd})[0]
return publicSection[len(r.username)+1 : len(r.username)+1+datelen]
}
type dateRequest struct {
clientInfo
user
}
type clientInfo struct {
reqTime uint64
ip uint32
ver uint32
}
type user struct {
id int
username string
}
<!
$ go run a.go
2014-01-01
dminpasswo
2014-03-04
r2password
As you can see, it works correctly to receive the dates of the users, but if the request has extra bytes on the username, part of the user's password gets returned instead. Not only that, but if you keep adding more bytes, it returns data from user2, which is supposed to be hidden. Why?
When executing main
's third line of code, user
and publicSection
in getData
are "admin�2014-01-0140�adminpassword" and "admin�2014-01-0140". But then it returns "dminpasswo". How is slicing publicSection
("admin�2014-01-0140") returning "dminpasswo"? It looks like a buffer overrun problem, but that shouldn't happen because Go is memory-safe. I even tried reading past the buffer of publicSection
by printing publicSection[len(publicSection)]
, but it panics as expected.
I also tried replacing all the []byte
with string
, and that fixes the issue for some reason.
答案1
得分: 6
切片表达式会将上限索引与切片的容量进行比较,而不仅仅是与切片的长度比较。实际上,你可以对切片进行超出其长度的切片操作。但是,你不能对底层数组进行超出边界的切片操作以访问未初始化的内存。
s := make([]int, 5, 10)
copy(s, []int{1, 2, 3, 4, 5})
fmt.Printf("len:%d cap:%d\n", len(s), cap(s))
// > len:5 cap:10
fmt.Printf("raw slice: %+v\n", s)
// > raw slice: [1 2 3 4 5]
fmt.Printf("sliced past length: %+v\n", s[:10])
// > sliced past length: [1 2 3 4 5 0 0 0 0 0]
// 引发 panic
_ = s[:11]
// > panic: runtime error: slice bounds out of range
如果你真的想防止对数组长度之外的部分进行切片操作,在 go1.3 或更高版本中,你可以在切片操作时将容量设置为第三个参数。
// 将容量设置为 5
s := s[:5:5]
// 现在这将引发 panic
_ = s[:6]
英文:
Slice expressions check the bounds of the upper index against the slice capacity, not just the length of the slice. In effect, you can slice past the length of the slice. You cannot however slice outside the bounds of the underlying array to access uninitialized memory.
http://play.golang.org/p/oIxXLG-YEV
s := make([]int, 5, 10)
copy(s, []int{1, 2, 3, 4, 5})
fmt.Printf("len:%d cap:%d\n", len(s), cap(s))
// > len:5 cap:10
fmt.Printf("raw slice: %+v\n", s)
// > raw slice: [1 2 3 4 5]
fmt.Printf("sliced past length: %+v\n", s[:10])
// > sliced past length: [1 2 3 4 5 0 0 0 0 0]
// panics
_ = s[:11]
// > panic: runtime error: slice bounds out of range
If you really want to prevent slicing past the length of an array, in go1.3 or later you can set the capacity as the third argument when slicing.
// set the capacity to 5
s := s[:5:5]
// now this will panic
_ = s[:6]
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论