切片操作导致缓冲区溢出并泄漏用户密码?

huangapple go评论82阅读模式
英文:

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中的userpublicSection分别是"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(&quot;len:%d cap:%d\n&quot;, len(s), cap(s))
// &gt; len:5 cap:10

fmt.Printf(&quot;raw slice: %+v\n&quot;, s)
// &gt; raw slice: [1 2 3 4 5]


fmt.Printf(&quot;sliced past length: %+v\n&quot;, s[:10])
// &gt; sliced past length: [1 2 3 4 5 0 0 0 0 0]

// panics
_ = s[:11]
// &gt; 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]

huangapple
  • 本文由 发表于 2014年9月3日 01:18:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/25628920.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定