[toc]
Original is not easy, welcome to the public number: Qi Ya cloud storage
Before a summary
One reader posted a piece of code he was working on with this question:
Code screenshot:
For research purposes, the code text is posted below:
package main
import (
"fmt"
"reflect"
"unsafe"
)
func StringToByte(key *string) []byte {
strPtr := (*reflect.SliceHeader)(unsafe.Pointer(key))
strPtr.Cap = strPtr.Len
b := *(*[]byte)(unsafe.Pointer(strPtr))
return b
}
func main(a) {
decryptContent := "/AvYEjm4g6xJ3LVrk2/Adk"
iv := decryptContent[0:16]
key := decryptContent[2:18]
fmt.Println(&iv)
fmt.Println(&key)
ivBytes := StringToByte(&iv)
keyBytes := StringToByte(&key)
fmt.Println(string(ivBytes))
fmt.Println(string(keyBytes))
}
Copy the code
Consider the first question: Why are errors reported?
I did compile it myself and got the following error. Why is this a problem? In fact, the title of the article has been explained, is to step on memory. So now we just have to analyze how to step on the memory, right?
Sh - 4.4 -# ./test
0xc0000821e0
0xc0000821f0
/AvYEjm4g6xJ3LVr
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x10 pc=0x45d1c6]
goroutine 1 [running]:
main.main()
/home/qiya/test.go:25 +0x37f
Copy the code
As I mentioned earlier in my in-depth look at Go Nil, it’s important to understand the variable structure itself and the managed structure.
A string variable, which itself takes 16 bytes, has a pointer to the block of memory where the string is stored, and a length field that identifies the length of the string. As follows:
type string struct {
uint8 *str;
int len;
}
Copy the code
The slice variable itself takes up 24 bytes and has three 8-byte fields. Data points to a block of byte memory, Len identifies the current valid element location, and Cap identifies the physical length of the dynamic array.
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
Copy the code
Now if we take a closer look at the StringToByte function in this program, we see that in main, we take the address of the key and iv variables themselves and pass them in as arguments to StringToByte, and then in this function, Forcing this address to type through the unsafe library, using it as a slice management structure, is important, and overwrites the strptr. Cap field. So that’s memory, 8 bytes more memory.
Let me analyze the code execution step by step:
Execute the following code:
decryptContent := "/AvYEjm4g6xJ3LVrk2/Adk"
iv := decryptContent[0:16]
key := decryptContent[2:18]
Copy the code
The memory stack is as follows:
Look at the code for 22,23:
ivBytes := StringToByte(&iv)
keyBytes := StringToByte(&key)
Copy the code
Then, after 22 lines of code executing the first StringToByte function (that is, the first memory step), we pass in the address of the variable iv, so we step back 8 bytes in the function StringToByte, that is, stamping out the first 8 bytes of the variable key. The effect is as follows:
The first eight bytes of the key are originally an address pointing to the memory string on the heap. Now it has been relentlessly stamped down to a round 16.
The StringToByte function then copies these 24 bytes to a local variable ivBytes on the stack with the following value:
*str => 0x4c253b
len => 16
cap => 16
Copy the code
Next, line 23 is run and the StringToByte function is executed again, this time passing in the address of the key variable, again 8 bytes backwards. Who did you step on this time? Take a look at the current contents of the key variable, hahaha, as follows:
(gdb) x/14gx 0xc0000821f0
0xc0000821f0: 0x0000000000000010 0x0000000000000010
0xc000082200: 0x0000000000000010 0x0000000000000000
Copy the code
0xC000082200 This address is also a foolproof, [khan]. You can see that the key variable itself becomes a double 16. We are looking at a variable on the stack:
And then,StringToByte
The function copies the 24 bytes to a local variable on the stackkeyBytes
, keyBytes
The value of the variable is as follows:
Look further back, lines 24,25:
fmt.Println(string(ivBytes))
fmt.Println(string(keyBytes))
Copy the code
These two lines of code do:
- The first
ivBytes
.keyBytes
The twoslice
intostring
Type, and print it out, - while
string(ivBytes)
The function called isruntime.slicebytetostring
The assembly can see the actual function called by string(ivBytes) as follows:
0x0000000000491d2f <+671>: callq 0x447f70 <runtime.slicebytetostring>
Copy the code
The sliceBytetoString prototype looks like this:
func slicebytetostring(buf *tmpBuf, b []byte) (str string){}Copy the code
So the first parameter, this is a pointer, and this pointer is the first field of the SliceHeader, which is the Data pointer field.
Moving on to the 24 lines of code that do not report an error, the ivBytes variable is fine. IvBytes can be converted to string. The ivBytes variable is as follows:
*str => 0x4c253b
len => 16
cap => 16
Copy the code
But 25 lines of code would cause panic (remember? In the first screenshot of the article, the panic position was line 25), but the key variable was trampled, causing the keyBytes variable to be incorrect.
*str => 16
len => 16
cap => 16
Copy the code
The value of 16 is passed as a pointer to the sliceBytetoString function, and the type is changed. If this does not cause panic of the invalid address, it will be really amazing.
That’s why panic came out.
Consider the second question: why is 22, 22,23 reversed?
How to step on the memory, has been clear, but this reader friend, and in-depth asked a sentence:
Yeah, why? All you have to do is draw a picture like the one above. 22, 2, 23 the only difference is whose memory you hit first.
keyBytes := StringToByte(&key)
ivBytes := StringToByte(&iv)
Copy the code
KeyBytes := StringToByte(&key) executes at the following address, but the iv address is before the key, so it is intact, and the unknown 8 bytes after the key are stamped. The final keyBytes value is as follows:
*str => 0x4c253d
len => 16
cap => 16
Copy the code
So ivBytes := StringToByte(&iv) is normal:
*str => 0x4c253b
len => 16
cap => 16
Copy the code
In the end, the key header is 16, but this does not affect the program, because we use keyBytes, ivBytes, so the program will work.
Consider the third question: How do I get it right?
package main
import (
"fmt"
"reflect"
"unsafe"
)
func StringToByte(key *string) []byte {
slic := reflect.SliceHeader{}
slic = *(*reflect.SliceHeader)(unsafe.Pointer(key))
slic.Cap = slic.Len
b := *(*[]byte)(unsafe.Pointer(&slic))
return b
}
func main(a) {
decryptContent := "/AvYEjm4g6xJ3LVrk2/Adk"
iv := decryptContent[0:16]
key := decryptContent[2:18]
fmt.Println(iv)
fmt.Println(key)
ivBytes := StringToByte(&iv)
keyBytes := StringToByte(&key)
fmt.Println(string(ivBytes))
fmt.Println(string(keyBytes))
}
Copy the code
I only changed the StringToByte function above, and only changed two lines of code.
slic := reflect.SliceHeader{}
slic = *(*reflect.SliceHeader)(unsafe.Pointer(key))
Copy the code
After adding these two lines of code, you will not step on the outside memory, thus eliminating the strange problems caused by stepping on the memory. If the specific principle of small partners are not clear, you can GDB analysis, or find me to communicate.
Of course, this reader is also in the middle of research and learning, so there should be no such strange code in real projects. But the thought that this little question raises is worth recording and learning.
Original is not easy, welcome to the public number: Qi Ya cloud storage
conclusion
- Go is not free of memory and other memory problems, all of which you may have to deal with if you want to get past Go’s type check.
unsafe
This library has such an obvious name, is to tell you not safe, use caution. So unless you have a reason to use it and you know the consequences of your actions, avoid it.- The string management structure is 16 bytes, slice management structure is 24 bytes, remember;
Original is not easy, welcome to the public number: Qi Ya cloud storage