-
Notifications
You must be signed in to change notification settings - Fork 369
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix invalid conversion between unsafe.Pointer and uintptr #6673
Conversation
a30bd29
to
b2e1f7a
Compare
syscallN := func(trap uintptr, args ...uintptr) (r1, r2 uintptr, err syscall.Errno) { | ||
// This mocks the 1st call to syscallN made by getIPForwardTable. It makes args[1] point to the table. | ||
if len(args) > 1 { | ||
ptr := unsafe.Pointer(args[1]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Somehow checkptr
thinks "pointer arithmetic result points to invalid allocation".
There is no problem when -race
is not used. Need more investigation.
7ed92a0
to
10983b1
Compare
@@ -307,11 +307,14 @@ type NetIOInterface interface { | |||
} | |||
|
|||
type netIO struct { | |||
syscallN func(trap uintptr, args ...uintptr) (r1, r2 uintptr, err syscall.Errno) | |||
syscallN func(trap uintptr, args ...uintptr) (r1, r2 uintptr, err syscall.Errno) | |||
getIPForwardTableFn func(family uint16, ipForwardTable **MibIPForwardTable) (errcode error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
couldn't we just fake syscallN
in tests, instead of faking individual functions like getIPForwardTable
? It seems that getIPForwardTable
is just a thin wrapper around a syscallN
call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's what I did in an earlier version: b2e1f7a. However, checkptr failed when --race
is enabled, as I commented in #6673 (comment). The error can be found here: https://github.com/antrea-io/antrea/actions/runs/10863106542/job/30146883490.
I suspect the compiler doesn't recognize the fake syscall and mistakenly raise the error. But even the latest version failed due to another checkptr error in code (as opposed the previous error in test code), and this error seems valid: the code shouldn't assign uintptr to a variable.
86e1eb8
to
db964e2
Compare
/test-windows-all |
if err != nil { | ||
return nil, os.NewSyscallError("iphlpapi.GetIpForwardTable", err) | ||
} | ||
rows := make([]MibIPForwardRow, table.NumEntries, table.NumEntries) | ||
defer n.freeMibTable(unsafe.Pointer(table)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason for removing “if table != nil” is that when err != nil, the memory release logic has already been correctly handled, so it won't cause a memory leak, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when err is not nil, the syscall failed and there is no memory to release, isn't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be, but it makes the original implementation a bit confusing. I don't understand why "if table != nil" check is needed before "if err != nil"(I recall that this part was copied from another project). @wenyingd Do you know where the source code is, and is there's any potential risk? Based on the function doc I found at https://learn.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getipforwardtable, the current code change is safe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://github.com/tailscale/winipcfg-go/blob/main/wt_mib_ipforward_row2.go#L58
This should be the place where we originally referred.
It should be good with the version in this change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code is safe unless the syscall returns a table and an error at the same time, which doesn't seem normal in golang. And wireguard doesn't try to free the memory either when there is an error: https://github.com/WireGuard/wireguard-windows/blob/e70799b1440690e7d4140bffc7c73baf903c7b54/tunnel/winipcfg/winipcfg.go#L145
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
}, | ||
} | ||
gotRows, gotErr := testNetIO.ListIPForwardRows(AF_INET) | ||
require.Nil(t, gotErr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: require.NoError
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
assert.True(t, freeMibTableCalled) | ||
// It verifies that the returned rows are independent copies, not referencing to the original table's memory, by | ||
// asserting they retain the exact same content as the original table whose rows have been reset by freeMibTable. | ||
expectedRows := []MibIPForwardRow{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it feels like we could declare row1
and row2
variables, to avoid repeating the row definitions in the test
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
uintptr cannot be stored in variable before conversion back to Pointer. This is because the variable is an integer with no pointer semantics, even if a uintptr holds the address of some object, the garbage collector will not update that uintptr's value if the object moves, nor will that uintptr keep the object from being reclaimed. The issue can be detected by checkptr when running unit test with race detector: checkptr: pointer arithmetic result points to invalid allocation ``` pFirstRow := uintptr(unsafe.Pointer(&table.Table[0])) row := *(*MibIPForwardRow)(unsafe.Pointer(pFirstRow + rowSize*uintptr(i))) ``` While it can be fixed by performing both conversions in the same expression like below, using `unsafe.Slice` to get the slice is much simpler. ``` row := *(*MibIPForwardRow)(unsafe.Pointer(uintptr(unsafe.Pointer(&table.Table[0])) + rowSize*uintptr(i))) ``` The patch also adds an unit test to validate it. Signed-off-by: Quan Tian <[email protected]>
db964e2
to
9396532
Compare
@wenyingd @XinShuYang will the windows e2e tests cover the updated code? |
The OSS e2e test cases can cover the function logic of the updated code. But the original failure is hit in scale setup, OSS e2e doesn't cover scale test. |
OK, it's fine as long as it covers the function logic. The original failure was due to misuse of memory, which has been covered by the new unit test. The unit test failed with the original code: https://github.com/tnqn/antrea/actions/runs/11344284459/job/31549023056?pr=46. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
/test-windows-all |
/test-windows-all |
/test-windows-all |
…6673) uintptr cannot be stored in variable before conversion back to Pointer. This is because the variable is an integer with no pointer semantics, even if a uintptr holds the address of some object, the garbage collector will not update that uintptr's value if the object moves, nor will that uintptr keep the object from being reclaimed. The issue can be detected by checkptr when running unit test with race detector: checkptr: pointer arithmetic result points to invalid allocation ``` pFirstRow := uintptr(unsafe.Pointer(&table.Table[0])) row := *(*MibIPForwardRow)(unsafe.Pointer(pFirstRow + rowSize*uintptr(i))) ``` While it can be fixed by performing both conversions in the same expression like below, using `unsafe.Slice` to get the slice is much simpler. ``` row := *(*MibIPForwardRow)(unsafe.Pointer(uintptr(unsafe.Pointer(&table.Table[0])) + rowSize*uintptr(i))) ``` The patch also adds an unit test to validate it. Signed-off-by: Quan Tian <[email protected]>
…6673) uintptr cannot be stored in variable before conversion back to Pointer. This is because the variable is an integer with no pointer semantics, even if a uintptr holds the address of some object, the garbage collector will not update that uintptr's value if the object moves, nor will that uintptr keep the object from being reclaimed. The issue can be detected by checkptr when running unit test with race detector: checkptr: pointer arithmetic result points to invalid allocation ``` pFirstRow := uintptr(unsafe.Pointer(&table.Table[0])) row := *(*MibIPForwardRow)(unsafe.Pointer(pFirstRow + rowSize*uintptr(i))) ``` While it can be fixed by performing both conversions in the same expression like below, using `unsafe.Slice` to get the slice is much simpler. ``` row := *(*MibIPForwardRow)(unsafe.Pointer(uintptr(unsafe.Pointer(&table.Table[0])) + rowSize*uintptr(i))) ``` The patch also adds an unit test to validate it. Signed-off-by: Quan Tian <[email protected]>
…6673) uintptr cannot be stored in variable before conversion back to Pointer. This is because the variable is an integer with no pointer semantics, even if a uintptr holds the address of some object, the garbage collector will not update that uintptr's value if the object moves, nor will that uintptr keep the object from being reclaimed. The issue can be detected by checkptr when running unit test with race detector: checkptr: pointer arithmetic result points to invalid allocation ``` pFirstRow := uintptr(unsafe.Pointer(&table.Table[0])) row := *(*MibIPForwardRow)(unsafe.Pointer(pFirstRow + rowSize*uintptr(i))) ``` While it can be fixed by performing both conversions in the same expression like below, using `unsafe.Slice` to get the slice is much simpler. ``` row := *(*MibIPForwardRow)(unsafe.Pointer(uintptr(unsafe.Pointer(&table.Table[0])) + rowSize*uintptr(i))) ``` The patch also adds an unit test to validate it. Signed-off-by: Quan Tian <[email protected]>
uintptr cannot be stored in variable before conversion back to Pointer. This is because the variable is an integer with no pointer semantics, even if a uintptr holds the address of some object, the garbage collector will not update that uintptr's value if the object moves, nor will that uintptr keep the object from being reclaimed. The issue can be detected by checkptr when running unit test with race detector: checkptr: pointer arithmetic result points to invalid allocation ``` pFirstRow := uintptr(unsafe.Pointer(&table.Table[0])) row := *(*MibIPForwardRow)(unsafe.Pointer(pFirstRow + rowSize*uintptr(i))) ``` While it can be fixed by performing both conversions in the same expression like below, using `unsafe.Slice` to get the slice is much simpler. ``` row := *(*MibIPForwardRow)(unsafe.Pointer(uintptr(unsafe.Pointer(&table.Table[0])) + rowSize*uintptr(i))) ``` The patch also adds an unit test to validate it. Signed-off-by: Quan Tian <[email protected]>
uintptr cannot be stored in variable before conversion back to Pointer. This is because the variable is an integer with no pointer semantics, even if a uintptr holds the address of some object, the garbage collector will not update that uintptr's value if the object moves, nor will that uintptr keep the object from being reclaimed. The issue can be detected by checkptr when running unit test with race detector: checkptr: pointer arithmetic result points to invalid allocation ``` pFirstRow := uintptr(unsafe.Pointer(&table.Table[0])) row := *(*MibIPForwardRow)(unsafe.Pointer(pFirstRow + rowSize*uintptr(i))) ``` While it can be fixed by performing both conversions in the same expression like below, using `unsafe.Slice` to get the slice is much simpler. ``` row := *(*MibIPForwardRow)(unsafe.Pointer(uintptr(unsafe.Pointer(&table.Table[0])) + rowSize*uintptr(i))) ``` The patch also adds an unit test to validate it. Signed-off-by: Quan Tian <[email protected]>
uintptr cannot be stored in variable before conversion back to Pointer. This is because the variable is an integer with no pointer semantics, even if a uintptr holds the address of some object, the garbage collector will not update that uintptr's value if the object moves, nor will that uintptr keep the object from being reclaimed. The issue can be detected by checkptr when running unit test with race detector: checkptr: pointer arithmetic result points to invalid allocation ``` pFirstRow := uintptr(unsafe.Pointer(&table.Table[0])) row := *(*MibIPForwardRow)(unsafe.Pointer(pFirstRow + rowSize*uintptr(i))) ``` While it can be fixed by performing both conversions in the same expression like below, using `unsafe.Slice` to get the slice is much simpler. ``` row := *(*MibIPForwardRow)(unsafe.Pointer(uintptr(unsafe.Pointer(&table.Table[0])) + rowSize*uintptr(i))) ``` The patch also adds an unit test to validate it. Signed-off-by: Quan Tian <[email protected]>
…6673) uintptr cannot be stored in variable before conversion back to Pointer. This is because the variable is an integer with no pointer semantics, even if a uintptr holds the address of some object, the garbage collector will not update that uintptr's value if the object moves, nor will that uintptr keep the object from being reclaimed. The issue can be detected by checkptr when running unit test with race detector: checkptr: pointer arithmetic result points to invalid allocation ``` pFirstRow := uintptr(unsafe.Pointer(&table.Table[0])) row := *(*MibIPForwardRow)(unsafe.Pointer(pFirstRow + rowSize*uintptr(i))) ``` While it can be fixed by performing both conversions in the same expression like below, using `unsafe.Slice` to get the slice is much simpler. ``` row := *(*MibIPForwardRow)(unsafe.Pointer(uintptr(unsafe.Pointer(&table.Table[0])) + rowSize*uintptr(i))) ``` The patch also adds an unit test to validate it. Signed-off-by: Quan Tian <[email protected]>
uintptr cannot be stored in variable before conversion back to Pointer. This is because the variable is an integer with no pointer semantics, even if a uintptr holds the address of some object, the garbage collector will not update that uintptr's value if the object moves, nor will that uintptr keep the object from being reclaimed.
The issue can be detected by checkptr when running unit test with race detector:
checkptr: pointer arithmetic result points to invalid allocation
While it can be fixed by performing both conversions in the same expression like below, using
unsafe.Slice
to get the slice is much simpler.The patch also adds an unit test to validate it.