-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(android): read hwcap using getauxval (v1)
There are Android devices where reading /proc/self/auxv fails with permission denied. Android is increasingly preventing apps and tools from accessing system functionality. Go uses /proc/self/auxv to fill cpu.ARM64. In turn, it uses the values in this data structure to choose whether to enable an assembly implementation of AES as well as to choose the order of the cipher suites in the TLS ClientHello. Unfortunately, as documented in ooni/probe#1444, reordering the ciphers in the ClientHello triggers censorship in Iran. Because new(?) Android devices do not allow Go to access /proc/self/auxv, we therefore see that OONI is not WAI. The most obvious fix to this issue could seem to teach crypto/tls to always put AES ciphers on top. Unfortunately, the pure Go AES implementation in the standard library is not constant time (while the one in assembly is constant time). Therefore, forcing AES to always be at the top of the ciphers list does not seem right. Thus, we can say that our real core problem is that Go is not able to know whether we support AES and PMULL on android/arm64. This diff attempts to solve this problem. The solution I have chosen is to implement a cpuproxy package exposing predicate functions describing the CPU's support for AES and PMULL. This cpuproxy package will use the existing logic for every GOOS and GOARCH except android/arm64. When we are in android/arm64, we use `getauxval(3)` to obtain the hardware capabilities and we use this information to implement the predicates. My initial attempt at solving this problem was to call `getauxval(3)` directly during the initialization of the `runtime` package. But it seems that when you use CGO you depend on the `syscall` package, and in turn `syscall` depends on `runtime`. So, when I was compiling OONI for Android, I always got a circular-import hard failure. Another approach could be that of reading `/proc/cpuinfo` inside of the `runtime` initialization. I did not explore it. This solution based on `libc` and `getauxval(3)` is not perfect but we already build OONI for Android with CGO enabled, so we should be fine in terms of the functionality that we require as OONI. I also adjusted the tests to reflect the fact that now there are more packages that depend on CGO (when using Android).
- Loading branch information
1 parent
9baddd3
commit 62177de
Showing
6 changed files
with
129 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
// +build android | ||
// +build arm64 | ||
|
||
// Copyright 2019 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
// | ||
// This file is based on the diff available at | ||
// https://go-review.googlesource.com/c/sys/+/197540/ | ||
|
||
package cpuproxy | ||
|
||
/* | ||
#include <sys/auxv.h> | ||
// getauxval is not available on Android until API level 20. Link it as a weak | ||
// symbol and check whether it is not NULL before using it. | ||
unsigned long getauxval(unsigned long type) __attribute__((weak)); | ||
*/ | ||
import "C" | ||
|
||
import "sync" | ||
|
||
// These constants are Linux specific. | ||
const ( | ||
_AT_HWCAP = 16 // hardware capability bit vector | ||
) | ||
|
||
// get returns the value of the getauxval auxiliary vector, or | ||
// zero where the functionality is unavailable. | ||
func get(t uint) uint { | ||
if C.getauxval == C.NULL { | ||
return 0 | ||
} | ||
return uint(C.getauxval(C.ulong(t))) | ||
} | ||
|
||
// dogethwcap returns the value of _AT_HWCAP by calling | ||
// the getauxval(3) function in libc (if available). | ||
func dogethwcap() uint { | ||
return get(_AT_HWCAP) | ||
} | ||
|
||
// These variables allow to cache getauxval(3) results. | ||
var ( | ||
once sync.Once | ||
hwcap uint | ||
) | ||
|
||
// gethwcap is like dogethwcap except that this function | ||
// ensures we call getauxval(3) just once. After the first | ||
// invocation we memoize the result. | ||
func gethwcap() uint { | ||
once.Do(func() { | ||
hwcap = dogethwcap() | ||
}) | ||
return hwcap | ||
} | ||
|
||
// HWCAP bits. These are exposed by Linux. | ||
const ( | ||
hwcap_AES = 1 << 3 | ||
hwcap_PMULL = 1 << 4 | ||
hwcap_SHA1 = 1 << 5 | ||
hwcap_SHA2 = 1 << 6 | ||
hwcap_CRC32 = 1 << 7 | ||
hwcap_ATOMICS = 1 << 8 | ||
hwcap_CPUID = 1 << 11 | ||
) | ||
|
||
// HasAES returns whether the CPU supports AES. | ||
func HasAES() bool { | ||
return (gethwcap() & hwcap_AES) != 0 | ||
} | ||
|
||
// HasGFMUL returns whether the CPU supports GFMUL. | ||
func HasGFMUL() bool { | ||
return (gethwcap() & hwcap_PMULL) != 0 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// +build !android !arm64 | ||
|
||
package cpuproxy | ||
|
||
import "internal/cpu" | ||
|
||
// HasAES returns whether the CPU supports AES. | ||
func HasAES() bool { | ||
return cpu.X86.HasAES || cpu.ARM64.HasAES | ||
} | ||
|
||
// HasGFMUL returns whether the CPU supports GFMUL. | ||
func HasGFMUL() bool { | ||
return cpu.X86.HasPCLMULQDQ || cpu.ARM64.HasPMULL | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// Package cpuproxy is a getauxval aware proxy for internal/cpu. | ||
// | ||
// The problem that we want to solve is that on Android there are | ||
// cases where reading /proc/self/auxv is not possible. | ||
// | ||
// This causes crypto/tls to not choose AES where it would otherwise | ||
// be possible, in turn causing censorship. See also the | ||
// https://github.com/ooni/probe/issues/1444 issue for more details. | ||
// | ||
// Ideally we would like to call getauxval(3) when initializing | ||
// the runtime package. However, runtime cannot use CGO. Doing that | ||
// leads to an import loop, so we cannot build. | ||
// | ||
// We could also try to parse /proc/cpuinfo (I didn't explore this route). | ||
// | ||
// The solution chosen here is to export predicates on the CPU | ||
// functionality. We limit ourselves to what we need in order to | ||
// choose AES in crypto/tls when the CPU supports it. | ||
// | ||
// The predicates use internal/cpu values for every GOOS/GOARCH except | ||
// android/arm64, where we call getauxval(3). | ||
package cpuproxy |