Skip to content

Commit

Permalink
fix(android): read hwcap using getauxval (v1)
Browse files Browse the repository at this point in the history
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
bassosimone committed Apr 21, 2021
1 parent 9baddd3 commit 62177de
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 9 deletions.
6 changes: 3 additions & 3 deletions src/crypto/aes/cipher_asm.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ package aes
import (
"crypto/cipher"
"crypto/internal/subtle"
"internal/cpu"
"internal/cpuproxy"
)

// defined in asm_*.s
Expand All @@ -27,8 +27,8 @@ type aesCipherAsm struct {
aesCipher
}

var supportsAES = cpu.X86.HasAES || cpu.ARM64.HasAES
var supportsGFMUL = cpu.X86.HasPCLMULQDQ || cpu.ARM64.HasPMULL
var supportsAES = cpuproxy.HasAES()
var supportsGFMUL = cpuproxy.HasGFMUL()

func newCipher(key []byte) (cipher.Block, error) {
if !supportsAES {
Expand Down
5 changes: 3 additions & 2 deletions src/crypto/tls/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"crypto/x509"
"errors"
"fmt"
"internal/cpuproxy"
"internal/cpu"
"io"
"net"
Expand Down Expand Up @@ -1436,8 +1437,8 @@ func defaultCipherSuitesTLS13() []uint16 {
}

var (
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ
hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
hasGCMAsmAMD64 = cpuproxy.HasAES() && cpuproxy.HasGFMUL()
hasGCMAsmARM64 = cpuproxy.HasAES() && cpuproxy.HasGFMUL()
// Keep in sync with crypto/aes/cipher_s390x.go.
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR && (cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM)

Expand Down
10 changes: 6 additions & 4 deletions src/go/build/deps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ var depsRules = `
# Bulk of the standard library must not use cgo.
# The prohibition stops at net and os/user.
C !< fmt, go/types, CRYPTO-MATH;
C !< fmt, go/types;
CGO, OS
< plugin;
Expand Down Expand Up @@ -377,9 +377,11 @@ var depsRules = `
NET, log
< net/mail;
CGO, internal/cpu, sync < internal/cpuproxy;
# CRYPTO is core crypto algorithms - no cgo, fmt, net.
# Unfortunately, stuck with reflect via encoding/binary.
encoding/binary, golang.org/x/sys/cpu, hash
encoding/binary, golang.org/x/sys/cpu, hash, internal/cpuproxy
< crypto
< crypto/subtle
< crypto/internal/subtle
Expand All @@ -388,7 +390,7 @@ var depsRules = `
crypto/sha1, crypto/sha256, crypto/sha512
< CRYPTO;
CGO, fmt, net !< CRYPTO;
fmt, net !< CRYPTO;
# CRYPTO-MATH is core bignum-based crypto - no cgo, net; fmt now ok.
CRYPTO, FMT, math/big
Expand All @@ -404,7 +406,7 @@ var depsRules = `
< crypto/ecdsa
< CRYPTO-MATH;
CGO, net !< CRYPTO-MATH;
net !< CRYPTO-MATH;
# TLS, Prince of Dependencies.
CRYPTO-MATH, NET, container/list, encoding/hex, encoding/pem
Expand Down
79 changes: 79 additions & 0 deletions src/internal/cpuproxy/cpuproxy_android_arm64.go
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
}
16 changes: 16 additions & 0 deletions src/internal/cpuproxy/cpuproxy_otherwise.go
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
}

22 changes: 22 additions & 0 deletions src/internal/cpuproxy/doc.go
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

0 comments on commit 62177de

Please sign in to comment.