Skip to content
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

Merged
merged 1 commit into from
Oct 17, 2024

Conversation

tnqn
Copy link
Member

@tnqn tnqn commented Sep 14, 2024

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.

@tnqn tnqn added area/test Issues or PRs related to unit and integration tests. kind/cleanup Categorizes issue or PR as related to cleaning up code, process, or technical debt. labels Sep 14, 2024
@tnqn tnqn force-pushed the unit-test-copy-rows branch 3 times, most recently from a30bd29 to b2e1f7a Compare September 14, 2024 14:17
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])
Copy link
Member Author

@tnqn tnqn Sep 14, 2024

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.

@tnqn tnqn force-pushed the unit-test-copy-rows branch 3 times, most recently from 7ed92a0 to 10983b1 Compare September 14, 2024 16:24
@@ -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)
Copy link
Contributor

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.

Copy link
Member Author

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.

@tnqn tnqn marked this pull request as draft September 19, 2024 02:53
@tnqn tnqn force-pushed the unit-test-copy-rows branch 3 times, most recently from 86e1eb8 to db964e2 Compare October 12, 2024 16:43
@tnqn tnqn changed the title Add unit test for ListIPForwardRows Fix invalid conversion between unsafe.Pointer and uintptr Oct 12, 2024
@tnqn tnqn added action/backport Indicates a PR that requires backports. action/release-note Indicates a PR that should be included in release notes. area/OS/windows Issues or PRs related to the Windows operating system. labels Oct 12, 2024
@tnqn tnqn marked this pull request as ready for review October 12, 2024 16:47
@tnqn
Copy link
Member Author

tnqn commented Oct 12, 2024

/test-windows-all

@tnqn tnqn removed area/test Issues or PRs related to unit and integration tests. kind/cleanup Categorizes issue or PR as related to cleaning up code, process, or technical debt. labels Oct 14, 2024
@luolanzone
Copy link
Contributor

If the previous way will lead to invalid conversion, I suppose we need this PR and #6664 for the final solution to fix Windows memory issue? @wenyingd If so, I think we need to cherry-pick this in downstream first before Antrea 2.2 is released.

if err != nil {
return nil, os.NewSyscallError("iphlpapi.GetIpForwardTable", err)
}
rows := make([]MibIPForwardRow, table.NumEntries, table.NumEntries)
defer n.freeMibTable(unsafe.Pointer(table))
Copy link
Contributor

@XinShuYang XinShuYang Oct 14, 2024

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?

Copy link
Member Author

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?

Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Member Author

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

antoninbas
antoninbas previously approved these changes Oct 14, 2024
Copy link
Contributor

@antoninbas antoninbas left a 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: require.NoError

Copy link
Member Author

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{
Copy link
Contributor

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

Copy link
Member Author

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]>
@tnqn
Copy link
Member Author

tnqn commented Oct 15, 2024

@wenyingd @XinShuYang will the windows e2e tests cover the updated code?

@wenyingd
Copy link
Contributor

@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.

@tnqn
Copy link
Member Author

tnqn commented Oct 15, 2024

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.

Copy link
Contributor

@antoninbas antoninbas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@XinShuYang
Copy link
Contributor

/test-windows-all

@tnqn
Copy link
Member Author

tnqn commented Oct 16, 2024

/test-windows-all
/skip-all

@tnqn
Copy link
Member Author

tnqn commented Oct 16, 2024

/test-windows-all

@tnqn tnqn merged commit aad06cd into antrea-io:main Oct 17, 2024
50 of 62 checks passed
@tnqn tnqn deleted the unit-test-copy-rows branch October 17, 2024 07:27
tnqn added a commit to XinShuYang/antrea that referenced this pull request Oct 17, 2024
…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]>
tnqn added a commit to XinShuYang/antrea that referenced this pull request Oct 17, 2024
…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]>
tnqn added a commit to XinShuYang/antrea that referenced this pull request Oct 17, 2024
…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]>
tnqn added a commit that referenced this pull request Oct 18, 2024
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]>
tnqn added a commit that referenced this pull request Oct 18, 2024
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]>
tnqn added a commit that referenced this pull request Oct 18, 2024
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]>
hangyan pushed a commit to hangyan/antrea that referenced this pull request Oct 29, 2024
…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]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
action/backport Indicates a PR that requires backports. action/release-note Indicates a PR that should be included in release notes. area/OS/windows Issues or PRs related to the Windows operating system.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants