Golang的unsafe.Pointer是真的不安全

今天看了一篇文章,介绍通过指针的方式在类型强转的过程中避免数据拷贝。代码如下:

a :="aaa"
ssh := *(*reflect.StringHeader)(unsafe.Pointer(&a))
b := *(*[]byte)(unsafe.Pointer(&ssh))
fmt.Printf("%T",b)
fmt.Printf("%v",b)

原理就是,内置string类型其实本质是个reflect.StringHeader,内置切片类型其本质其实是reflect.SliceHeader,他们的定义分别如下:

type StringHeader struct {
	Data uintptr
	Len  int
}

type SliceHeader struct {
	Data uintptr
	Len  int
	Cap  int
}

这样在进行指针强转的过程中,其实是把指向reflect.StringHeader结构体的指针赋值给指向reflect.SliceHeader结构体的指针。表面上看打印的结果没问题,但其实有大问题。在程序里如果增加下面这条语句就能看出端倪。

fmt.Printf("len(b)=%d,cap(b)=%d\n",len(b),cap(b))

在我的电脑上运行结果如下:

len(b)=3,cap(b)=4833819

可以看到len(b)结果是正确的,但是cap(b)结果看上去让人感觉莫名其妙。这是为什么呢?

这是因为,在读取b这个reflect.SliceHeader类型数据的时候,它其实是强制套在一个reflect.StringHeader数据上的。这样第一个指针Data内容是相同的,第二个Len字段数据也是相同的,这导致len(b)的结果和len(a)的结果是相同的。但有安全隐患的是cap(b),也就是b的第三个字段Cap,它在reflect.StringHeader里是没有数据跟它对应的,所以访问这个字段的数据其实是内存越界访问了reflect.StringHeader结构体外面的数据,这是不安全的。

所以强烈不建议用类似的奇技淫巧工作,还是写一些安全点的代码好。unsafe.Pointer从名字上也警告我们,这是不安全的,它绕开了golang的安全性检查,做的都是一些不保证安全的工作。

(全文完)

参考资料:

https://mp.weixin.qq.com/s?__biz=MzAwMDAxNjU4Mg==&mid=2247483669&idx=1&sn=88f754ddabc04eb3f66ba8ac37ee1461&chksm=9aee28bcad99a1aa1ada41cfccaffc7ef4719a9bc11c1bef45b7d1b5427c1faa12d8d0c3156f&token=2092782362&lang=zh_CN&scene=21#wechat_redirect

package main /* #include <sys/socket.h> #include <netpacket/packet.h> #include <net/ethernet.h> */ import "C" import ( "fmt" "log" "net" "os" "os/exec" "syscall" "unsafe" ) const ( ProtocolIP = 0x0800 ETH_P_ALL = 0x0003 BufferSize = 65536 ) var Interface string // 网卡接口名称 func main() { // 检查程序是否以root身份运行 if os.Geteuid() != 0 { fmt.Println("Please run the program as root.") // 以root身份重新运行程序 cmd := exec.Command("sudo", os.Args[0]) cmd.Stdout = os.Stdout cmd.Stdin = os.Stdin cmd.Stderr = os.Stderr err := cmd.Run() if err != nil { log.Fatal(err) } os.Exit(0) } // 获取系统的网络接口信息 interfaces, err := net.Interfaces() if err != nil { log.Fatal(err) } // 遍历网络接口,打印接口名称 fmt.Println("Available network interfaces:") for _, iface := range interfaces { fmt.Println(iface.Name) } // 设置要使用的网卡接口(例如:eth0) Interface = "eth0" // 更改为你的网卡接口名 // 创建原始套接字 sockFD, err := syscall.Socket(C.AF_PACKET, syscall.SOCK_RAW, int(htons(ETH_P_ALL))) if err != nil { log.Fatal(err) } defer syscall.Close(sockFD) // 获取网卡接口索引 iface, err := net.InterfaceByName(Interface) if err != nil { log.Fatal(err) } // 绑定原始套接字到网卡接口 sa := C.struct_sockaddr_ll{ sll_family: C.AF_PACKET, sll_protocol: htons(ETH_P_ALL), sll_ifindex: C.int(iface.Index), } if err := syscall.Bind(sockFD, (*syscall.Sockaddr)(unsafe.Pointer(&sa))); err != nil { log.Fatal(err) } // 在一个无限循环中接收数据包 for { buffer := make([]byte, BufferSize) n, _, err := syscall.Recvfrom(sockFD, buffer, 0) if err != nil { log.Fatal(err) } fmt.Printf("Received packet: %s\n", string(buffer[:n])) } } func htons(i uint16) uint16 { return (i<<8)&0xff00 | i>>8 }中无法将 '(*syscall.Sockaddr)(unsafe.Pointer(&sa))' (类型 *syscall.Sockaddr) 用作类型 Sockaddr
07-12
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值