Skip to content

Commit 05515e5

Browse files
authored
Use cgo noescape/nocallback instead of C wrappers (#249)
* use cgo noescape/nocallback instead of C wrappers * bump Go test versions * skip TestAllocationsif asan is enabled
1 parent 136f75d commit 05515e5

11 files changed

+71
-109
lines changed

.github/workflows/test.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ jobs:
55
strategy:
66
fail-fast: false
77
matrix:
8-
go-version: [1.22.x, 1.23.x]
8+
go-version: [1.23.x, 1.24.x]
99
openssl-version: [1.1.0, 1.1.1, 3.0.1, 3.0.13, 3.1.5, 3.2.1, 3.3.0, 3.3.1]
1010
runs-on: ubuntu-20.04
1111
steps:

asan_disabled_test.go

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//go:build !asan
2+
3+
package openssl_test
4+
5+
func Asan() bool {
6+
return false
7+
}

asan_enabled_test.go

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//go:build asan
2+
3+
package openssl_test
4+
5+
func Asan() bool {
6+
return true
7+
}

cgo_go124.go

+18-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,22 @@ package openssl
1313
// observed to benefit from these directives, not every function that is merely
1414
// expected to meet the noescape/nocallback criteria.
1515

16-
// #cgo noescape go_openssl_RAND_bytes
17-
// #cgo nocallback go_openssl_RAND_bytes
16+
/*
17+
#cgo noescape go_openssl_RAND_bytes
18+
#cgo nocallback go_openssl_RAND_bytes
19+
#cgo noescape go_openssl_EVP_EncryptUpdate
20+
#cgo nocallback go_openssl_EVP_EncryptUpdate
21+
#cgo noescape go_openssl_EVP_DecryptUpdate
22+
#cgo nocallback go_openssl_EVP_DecryptUpdate
23+
#cgo noescape go_openssl_EVP_CipherUpdate
24+
#cgo nocallback go_openssl_EVP_CipherUpdate
25+
#cgo noescape go_openssl_EVP_PKEY_derive
26+
#cgo nocallback go_openssl_EVP_PKEY_derive
27+
#cgo noescape go_openssl_EVP_PKEY_get_raw_public_key
28+
#cgo nocallback go_openssl_EVP_PKEY_get_raw_public_key
29+
#cgo noescape go_openssl_EVP_PKEY_get_raw_private_key
30+
#cgo nocallback go_openssl_EVP_PKEY_get_raw_private_key
31+
#cgo noescape go_openssl_EVP_DigestSign
32+
#cgo nocallback go_openssl_EVP_DigestSign
33+
*/
1834
import "C"

cipher.go

+8-4
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,8 @@ func (c *evpCipher) encrypt(dst, src []byte) error {
179179
}
180180
defer C.go_openssl_EVP_CIPHER_CTX_free(enc_ctx)
181181

182-
if C.go_openssl_EVP_EncryptUpdate_wrapper(enc_ctx, base(dst), base(src), C.int(c.blockSize)) != 1 {
182+
var outl C.int
183+
if C.go_openssl_EVP_EncryptUpdate(enc_ctx, base(dst), &outl, base(src), C.int(c.blockSize)) != 1 {
183184
return errors.New("EncryptUpdate failed")
184185
}
185186
runtime.KeepAlive(c)
@@ -208,7 +209,8 @@ func (c *evpCipher) decrypt(dst, src []byte) error {
208209
return errors.New("could not disable cipher padding")
209210
}
210211

211-
C.go_openssl_EVP_DecryptUpdate_wrapper(dec_ctx, base(dst), base(src), C.int(c.blockSize))
212+
var outl C.int
213+
C.go_openssl_EVP_DecryptUpdate(dec_ctx, base(dst), &outl, base(src), C.int(c.blockSize))
212214
runtime.KeepAlive(c)
213215
return nil
214216
}
@@ -235,7 +237,8 @@ func (x *cipherCBC) CryptBlocks(dst, src []byte) {
235237
panic("crypto/cipher: output smaller than input")
236238
}
237239
if len(src) > 0 {
238-
if C.go_openssl_EVP_CipherUpdate_wrapper(x.ctx, base(dst), base(src), C.int(len(src))) != 1 {
240+
var outl C.int
241+
if C.go_openssl_EVP_CipherUpdate(x.ctx, base(dst), &outl, base(src), C.int(len(src))) != 1 {
239242
panic("crypto/cipher: CipherUpdate failed")
240243
}
241244
runtime.KeepAlive(x)
@@ -278,7 +281,8 @@ func (x *cipherCTR) XORKeyStream(dst, src []byte) {
278281
if len(src) == 0 {
279282
return
280283
}
281-
if C.go_openssl_EVP_EncryptUpdate_wrapper(x.ctx, base(dst), base(src), C.int(len(src))) != 1 {
284+
var outl C.int
285+
if C.go_openssl_EVP_EncryptUpdate(x.ctx, base(dst), &outl, base(src), C.int(len(src))) != 1 {
282286
panic("crypto/cipher: EncryptUpdate failed")
283287
}
284288
runtime.KeepAlive(x)

ecdh.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -249,13 +249,13 @@ func ECDH(priv *PrivateKeyECDH, pub *PublicKeyECDH) ([]byte, error) {
249249
if C.go_openssl_EVP_PKEY_derive_set_peer(ctx, pub._pkey) != 1 {
250250
return nil, newOpenSSLError("EVP_PKEY_derive_set_peer")
251251
}
252-
r := C.go_openssl_EVP_PKEY_derive_wrapper(ctx, nil, 0)
253-
if r.result != 1 {
254-
return nil, newOpenSSLError("EVP_PKEY_derive_init")
252+
var keylen C.size_t
253+
if C.go_openssl_EVP_PKEY_derive(ctx, nil, &keylen) != 1 {
254+
return nil, newOpenSSLError("EVP_PKEY_derive")
255255
}
256-
out := make([]byte, r.keylen)
257-
if C.go_openssl_EVP_PKEY_derive_wrapper(ctx, base(out), r.keylen).result != 1 {
258-
return nil, newOpenSSLError("EVP_PKEY_derive_init")
256+
out := make([]byte, keylen)
257+
if C.go_openssl_EVP_PKEY_derive(ctx, base(out), &keylen) != 1 {
258+
return nil, newOpenSSLError("EVP_PKEY_derive")
259259
}
260260
return out, nil
261261
}

ed25519.go

+12-12
Original file line numberDiff line numberDiff line change
@@ -155,12 +155,12 @@ func NewPrivateKeyEd25519FromSeed(seed []byte) (*PrivateKeyEd25519, error) {
155155
}
156156

157157
func extractPKEYPubEd25519(pkey C.GO_EVP_PKEY_PTR, pub []byte) error {
158-
r := C.go_openssl_EVP_PKEY_get_raw_public_key_wrapper(pkey, base(pub), C.size_t(publicKeySizeEd25519))
159-
if r.result != 1 {
158+
keylen := C.size_t(publicKeySizeEd25519)
159+
if C.go_openssl_EVP_PKEY_get_raw_public_key(pkey, base(pub), &keylen) != 1 {
160160
return newOpenSSLError("EVP_PKEY_get_raw_public_key")
161161
}
162-
if r.len != publicKeySizeEd25519 {
163-
return errors.New("ed25519: bad public key length: " + strconv.Itoa(int(r.len)))
162+
if int(keylen) != publicKeySizeEd25519 {
163+
return errors.New("ed25519: bad public key length: " + strconv.Itoa(int(keylen)))
164164
}
165165
return nil
166166
}
@@ -169,12 +169,12 @@ func extractPKEYPrivEd25519(pkey C.GO_EVP_PKEY_PTR, priv []byte) error {
169169
if err := extractPKEYPubEd25519(pkey, priv[seedSizeEd25519:]); err != nil {
170170
return err
171171
}
172-
r := C.go_openssl_EVP_PKEY_get_raw_private_key_wrapper(pkey, base(priv), C.size_t(seedSizeEd25519))
173-
if r.result != 1 {
172+
keylen := C.size_t(seedSizeEd25519)
173+
if C.go_openssl_EVP_PKEY_get_raw_private_key(pkey, base(priv), &keylen) != 1 {
174174
return newOpenSSLError("EVP_PKEY_get_raw_private_key")
175175
}
176-
if r.len != seedSizeEd25519 {
177-
return errors.New("ed25519: bad private key length: " + strconv.Itoa(int(r.len)))
176+
if int(keylen) != seedSizeEd25519 {
177+
return errors.New("ed25519: bad private key length: " + strconv.Itoa(int(keylen)))
178178
}
179179
return nil
180180
}
@@ -200,12 +200,12 @@ func signEd25519(priv *PrivateKeyEd25519, sig, message []byte) error {
200200
if C.go_openssl_EVP_DigestSignInit(ctx, nil, nil, nil, priv._pkey) != 1 {
201201
return newOpenSSLError("EVP_DigestSignInit")
202202
}
203-
r := C.go_openssl_EVP_DigestSign_wrapper(ctx, base(sig), C.size_t(signatureSizeEd25519), base(message), C.size_t(len(message)))
204-
if r.result != 1 {
203+
siglen := C.size_t(signatureSizeEd25519)
204+
if C.go_openssl_EVP_DigestSign(ctx, base(sig), &siglen, base(message), C.size_t(len(message))) != 1 {
205205
return newOpenSSLError("EVP_DigestSign")
206206
}
207-
if r.siglen != signatureSizeEd25519 {
208-
return errors.New("ed25519: bad signature length: " + strconv.Itoa(int(r.siglen)))
207+
if int(siglen) != signatureSizeEd25519 {
208+
return errors.New("ed25519: bad signature length: " + strconv.Itoa(int(siglen)))
209209
}
210210
return nil
211211
}

goopenssl.h

-76
Original file line numberDiff line numberDiff line change
@@ -76,82 +76,6 @@ go_hash_sum(GO_EVP_MD_CTX_PTR ctx, GO_EVP_MD_CTX_PTR ctx2, unsigned char *out)
7676
return go_openssl_EVP_DigestFinal(ctx2, out, NULL);
7777
}
7878

79-
// These wrappers allocate out_len on the C stack to avoid having to pass a pointer from Go, which would escape to the heap.
80-
// Use them only in situations where the output length can be safely discarded.
81-
static inline int
82-
go_openssl_EVP_EncryptUpdate_wrapper(GO_EVP_CIPHER_CTX_PTR ctx, unsigned char *out, const unsigned char *in, int in_len)
83-
{
84-
int len;
85-
return go_openssl_EVP_EncryptUpdate(ctx, out, &len, in, in_len);
86-
}
87-
88-
static inline int
89-
go_openssl_EVP_DecryptUpdate_wrapper(GO_EVP_CIPHER_CTX_PTR ctx, unsigned char *out, const unsigned char *in, int in_len)
90-
{
91-
int len;
92-
return go_openssl_EVP_DecryptUpdate(ctx, out, &len, in, in_len);
93-
}
94-
95-
static inline int
96-
go_openssl_EVP_CipherUpdate_wrapper(GO_EVP_CIPHER_CTX_PTR ctx, unsigned char *out, const unsigned char *in, int in_len)
97-
{
98-
int len;
99-
return go_openssl_EVP_CipherUpdate(ctx, out, &len, in, in_len);
100-
}
101-
102-
// These wrappers also allocate length variables on the C stack to avoid escape to the heap, but do return the result.
103-
// A struct is returned that contains multiple return values instead of OpenSSL's approach of using pointers.
104-
105-
typedef struct
106-
{
107-
int result;
108-
size_t keylen;
109-
} go_openssl_EVP_PKEY_derive_wrapper_out;
110-
111-
static inline go_openssl_EVP_PKEY_derive_wrapper_out
112-
go_openssl_EVP_PKEY_derive_wrapper(GO_EVP_PKEY_CTX_PTR ctx, unsigned char *key, size_t keylen)
113-
{
114-
go_openssl_EVP_PKEY_derive_wrapper_out r = {0, keylen};
115-
r.result = go_openssl_EVP_PKEY_derive(ctx, key, &r.keylen);
116-
return r;
117-
}
118-
119-
typedef struct
120-
{
121-
int result;
122-
size_t len;
123-
} go_openssl_EVP_PKEY_get_raw_key_out;
124-
125-
static inline go_openssl_EVP_PKEY_get_raw_key_out
126-
go_openssl_EVP_PKEY_get_raw_public_key_wrapper(const GO_EVP_PKEY_PTR pkey, unsigned char *pub, size_t len)
127-
{
128-
go_openssl_EVP_PKEY_get_raw_key_out r = {0, len};
129-
r.result = go_openssl_EVP_PKEY_get_raw_public_key(pkey, pub, &r.len);
130-
return r;
131-
}
132-
133-
static inline go_openssl_EVP_PKEY_get_raw_key_out
134-
go_openssl_EVP_PKEY_get_raw_private_key_wrapper(const GO_EVP_PKEY_PTR pkey, unsigned char *priv, size_t len)
135-
{
136-
go_openssl_EVP_PKEY_get_raw_key_out r = {0, len};
137-
r.result = go_openssl_EVP_PKEY_get_raw_private_key(pkey, priv, &r.len);
138-
return r;
139-
}
140-
141-
typedef struct
142-
{
143-
int result;
144-
size_t siglen;
145-
} go_openssl_EVP_DigestSign_wrapper_out;
146-
147-
static inline go_openssl_EVP_DigestSign_wrapper_out
148-
go_openssl_EVP_DigestSign_wrapper(GO_EVP_MD_CTX_PTR ctx, unsigned char *sigret, size_t siglen, const unsigned char *tbs, size_t tbslen)
149-
{
150-
go_openssl_EVP_DigestSign_wrapper_out r = {0, siglen};
151-
r.result = go_openssl_EVP_DigestSign(ctx, sigret, &r.siglen, tbs, tbslen);
152-
return r;
153-
}
154-
15579
// These wrappers allocate out_len on the C stack, and check that it matches the expected
15680
// value, to avoid having to pass a pointer from Go, which would escape to the heap.
15781

hkdf.go

+8-7
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ func (c *hkdf1) Read(p []byte) (int, error) {
102102
}
103103
c.buf = append(c.buf, make([]byte, needLen)...)
104104
outLen := C.size_t(prevLen + needLen)
105-
if C.go_openssl_EVP_PKEY_derive_wrapper(c.ctx, base(c.buf), outLen).result != 1 {
105+
if C.go_openssl_EVP_PKEY_derive(c.ctx, base(c.buf), &outLen) != 1 {
106106
return 0, newOpenSSLError("EVP_PKEY_derive")
107107
}
108108
n := copy(p, c.buf[prevLen:outLen])
@@ -126,15 +126,15 @@ func ExtractHKDF(h func() hash.Hash, secret, salt []byte) ([]byte, error) {
126126
return nil, err
127127
}
128128
defer C.go_openssl_EVP_PKEY_CTX_free(ctx)
129-
r := C.go_openssl_EVP_PKEY_derive_wrapper(ctx, nil, 0)
130-
if r.result != 1 {
129+
var keylen C.size_t
130+
if C.go_openssl_EVP_PKEY_derive(ctx, nil, &keylen) != 1 {
131131
return nil, newOpenSSLError("EVP_PKEY_derive_init")
132132
}
133-
out := make([]byte, r.keylen)
134-
if C.go_openssl_EVP_PKEY_derive_wrapper(ctx, base(out), r.keylen).result != 1 {
133+
out := make([]byte, keylen)
134+
if C.go_openssl_EVP_PKEY_derive(ctx, base(out), &keylen) != 1 {
135135
return nil, newOpenSSLError("EVP_PKEY_derive")
136136
}
137-
return out[:r.keylen], nil
137+
return out[:keylen], nil
138138
case 3:
139139
ctx, err := newHKDFCtx3(md, C.GO_EVP_KDF_HKDF_MODE_EXTRACT_ONLY, secret, salt, nil, nil)
140140
if err != nil {
@@ -170,7 +170,8 @@ func ExpandHKDFOneShot(h func() hash.Hash, pseudorandomKey, info []byte, keyLeng
170170
return nil, err
171171
}
172172
defer C.go_openssl_EVP_PKEY_CTX_free(ctx)
173-
if C.go_openssl_EVP_PKEY_derive_wrapper(ctx, base(out), C.size_t(keyLength)).result != 1 {
173+
keylen := C.size_t(keyLength)
174+
if C.go_openssl_EVP_PKEY_derive(ctx, base(out), &keylen) != 1 {
174175
return nil, newOpenSSLError("EVP_PKEY_derive")
175176
}
176177
case 3:

rand_test.go

+3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ func TestRand(t *testing.T) {
1414
}
1515

1616
func TestAllocations(t *testing.T) {
17+
if Asan() {
18+
t.Skip("skipping allocations test with sanitizers")
19+
}
1720
n := int(testing.AllocsPerRun(10, func() {
1821
buf := make([]byte, 32)
1922
openssl.RandReader.Read(buf)

tls1prf.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ func tls1PRF1(result, secret, label, seed []byte, md C.GO_EVP_MD_PTR) error {
9797
return newOpenSSLError("EVP_PKEY_CTX_add1_tls1_prf_seed")
9898
}
9999
outLen := C.size_t(len(result))
100-
if C.go_openssl_EVP_PKEY_derive_wrapper(ctx, base(result), outLen).result != 1 {
100+
if C.go_openssl_EVP_PKEY_derive(ctx, base(result), &outLen) != 1 {
101101
return newOpenSSLError("EVP_PKEY_derive")
102102
}
103103
// The Go standard library expects TLS1PRF to return the requested number of bytes,

0 commit comments

Comments
 (0)