File libgcrypt-CVE-2024-2236_08.patch of Package libgcrypt.39917
From 5830e9531f8dab472605707df4ce62c886fc9323 Mon Sep 17 00:00:00 2001
From: Jakub Jelen <jjelen@redhat.com>
Date: Fri, 26 Apr 2024 16:39:18 +0200
Subject: [PATCH 08/11] Constant time conversion of the message to the SEXP
* cipher/rsa.c (rsa_decrypt): Use constant time conversion to SEXP after
decryption.
* src/const-time.c (_gcry_ct_memcpy): Constant time memcpy. New.
* src/const-time.h (ct_lt): New.
(ct_char_gen{,_inv}_mask): New.
* src/sexp.c (do_vsexp_sscan): Introduce constant time version of %b
--
The current conversion of the message back to the SEXP buffer leaks the
length of the decrypted message. This introduces a constant-time variant
that should run for the same time for any input length.
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
---
cipher/rsa.c | 4 ++++
src/const-time.c | 24 ++++++++++++++++++++++++
src/const-time.h | 12 ++++++++++++
src/sexp.c | 39 +++++++++++++++++++++++++++++++++------
4 files changed, 73 insertions(+), 6 deletions(-)
Index: libgcrypt-1.6.1/cipher/rsa.c
===================================================================
--- libgcrypt-1.6.1.orig/cipher/rsa.c
+++ libgcrypt-1.6.1/cipher/rsa.c
@@ -1511,7 +1511,11 @@ rsa_decrypt (gcry_sexp_t *r_plain, gcry_
rc = _gcry_rsa_pkcs1_decode_for_enc (&unpad, &unpadlen, ctx.nbits, plain);
mpi_free (plain);
plain = NULL;
+#ifdef WITH_MARVIN_WORKAROUND
+ rc_sexp = sexp_build (&result, NULL, "(value %c)", (int)unpadlen, unpad, (nbits + 7) / 8);
+#else
sexp_build (&result, NULL, "(value %b)", (int)unpadlen, unpad);
+#endif /* WITH_MARVIN_WORKAROUND */
*r_plain = sexp_null_cond (result, !!rc);
dummy = sexp_null_cond (result,!rc);
sexp_release (dummy);
Index: libgcrypt-1.6.1/src/const-time.c
===================================================================
--- libgcrypt-1.6.1.orig/src/const-time.c
+++ libgcrypt-1.6.1/src/const-time.c
@@ -49,3 +49,47 @@ ct_memequal (const void *b1, const void
*/
return (1 & ((res - 1) >> 8));
}
+
+/*
+ * Copy LEN bytes from memory area SRC to memory area DST, when
+ * OP_ENABLED=1. When DST <= SRC, the memory areas may overlap. When
+ * DST > SRC, the memory areas must not overlap.
+ */
+void
+_gcry_ct_memmov_cond (void *dst, const void *src, size_t len,
+ unsigned long op_enable)
+{
+ /* Note: dual mask with AND/OR used for EM leakage mitigation */
+ unsigned char mask1 = ct_ulong_gen_mask(op_enable);
+ unsigned char mask2 = ct_ulong_gen_inv_mask(op_enable);
+ unsigned char *b_dst = dst;
+ const unsigned char *b_src = src;
+ size_t i;
+
+ for (i = 0; i < len; i++)
+ b_dst[i] = (b_dst[i] & mask2) | (b_src[i] & mask1);
+}
+
+/*
+ * Copy LEN bytes from memory area SRC to memory area DST. The
+ * bytes are read up to the BUFFER_LEN size to keep the operation
+ * not dependent on the message length (both SRC and DST buffers
+ * need to have this size!).
+ */
+void
+_gcry_ct_memcpy (void *dst, const void *src, size_t len, size_t buffer_len)
+{
+ unsigned char mask_a, mask_b;
+ unsigned char *b_dst = dst;
+ const unsigned char *b_src = src;
+ unsigned int writing;
+ size_t i;
+
+ for (i = 0; i < buffer_len; i++)
+ {
+ writing = ct_lt (i, len);
+ mask_b = ct_uchar_gen_inv_mask (writing);
+ mask_a = ct_uchar_gen_mask (writing);
+ b_dst[i] = (b_src[i] & mask_a) | (b_dst[i] & mask_b);
+ }
+}
Index: libgcrypt-1.6.1/src/const-time.h
===================================================================
--- libgcrypt-1.6.1.orig/src/const-time.h
+++ libgcrypt-1.6.1/src/const-time.h
@@ -24,6 +24,21 @@ ct_lt_s (size_t a, size_t b)
return ((a ^ ((a ^ b) | ((a - b) ^ b))) >> (sizeof(size_t) * 8 - 1));
}
+#include "types.h"
+
+#define ct_memmov_cond _gcry_ct_memmov_cond
+#define ct_memequal _gcry_ct_memequal
+#define ct_memcpy _gcry_ct_memcpy
+
+/*
+ * Return 1 if A < B and return 0 otherwise.
+ */
+static inline int
+ct_lt (unsigned int a, unsigned int b)
+{
+ return (a ^ ((a ^ b) | ((a - b) ^ b))) >> (sizeof(unsigned int)*8 - 1);
+}
+
/*
* Return 1 if it's not same, 0 if same.
*/
@@ -43,6 +58,58 @@ ct_not_equal_byte (unsigned char b0, uns
}
/*
+ * Return all bits set if A is 1 and return 0 otherwise.
+ */
+#ifdef HAVE_GCC_ASM_VOLATILE_MEMORY
+# define DEFINE_CT_TYPE_GEN_MASK(name, type) \
+ static inline type \
+ ct_##name##_gen_mask (unsigned long op_enable) \
+ { \
+ type mask = -(type)op_enable; \
+ asm volatile ("\n" : "+r" (mask) :: "memory"); \
+ return mask; \
+ }
+#else
+# define DEFINE_CT_TYPE_GEN_MASK(name, type) \
+ static inline type \
+ ct_##name##_gen_mask (unsigned long op_enable) \
+ { \
+ type mask = (type)_gcry_ct_vzero - (type)op_enable; \
+ return mask; \
+ }
+#endif
+
+DEFINE_CT_TYPE_GEN_MASK(uintptr, uintptr_t)
+DEFINE_CT_TYPE_GEN_MASK(ulong, unsigned long)
+DEFINE_CT_TYPE_GEN_MASK(uchar, unsigned char)
+
+/*
+ * Return all bits set if A is 0 and return 1 otherwise.
+ */
+#ifdef HAVE_GCC_ASM_VOLATILE_MEMORY
+# define DEFINE_CT_TYPE_GEN_INV_MASK(name, type) \
+ static inline type \
+ ct_##name##_gen_inv_mask (unsigned long op_enable) \
+ { \
+ type mask = (type)op_enable - (type)1; \
+ asm volatile ("\n" : "+r" (mask) :: "memory"); \
+ return mask; \
+ }
+#else
+# define DEFINE_CT_TYPE_GEN_INV_MASK(name, type) \
+ static inline type \
+ ct_##name##_gen_inv_mask (unsigned long op_enable) \
+ { \
+ type mask = (type)op_enable - (type)_gcry_ct_vone; \
+ return mask; \
+ }
+#endif
+
+DEFINE_CT_TYPE_GEN_INV_MASK(uintptr, uintptr_t)
+DEFINE_CT_TYPE_GEN_INV_MASK(ulong, unsigned long)
+DEFINE_CT_TYPE_GEN_INV_MASK(uchar, unsigned char)
+
+/*
* Return NULL when OP_ENABLED=1
* otherwise, return W
*/
@@ -57,4 +124,12 @@ sexp_null_cond (gcry_sexp_t w, unsigned
/* Compare byte-arrays of length LEN, return 1 if it's same, 0 otherwise.
We use pointer of void *, so that it can be used with any structure. */
-unsigned int ct_memequal (const void *b1, const void *b2, size_t len);
+unsigned int _gcry_ct_memequal (const void *b1, const void *b2, size_t len);
+/*
+ * Copy LEN bytes from memory area SRC to memory area DST, when
+ * OP_ENABLED=1. When DST <= SRC, the memory areas may overlap. When
+ * DST > SRC, the memory areas must not overlap.
+ */
+void _gcry_ct_memmov_cond (void *dst, const void *src, size_t len,
+ unsigned long op_enable);
+void _gcry_ct_memcpy (void *dst, const void *src, size_t len, size_t buffer_len);
Index: libgcrypt-1.6.1/src/sexp.c
===================================================================
--- libgcrypt-1.6.1.orig/src/sexp.c
+++ libgcrypt-1.6.1/src/sexp.c
@@ -31,6 +31,7 @@
#define GCRYPT_NO_MPI_MACROS 1
#include "g10lib.h"
+#include "const-time.h"
/* Notes on the internal memory layout.
@@ -1079,6 +1080,10 @@ unquote_string (const char *string, size
* %d - integer stored as string (no autoswitch to secure allocation)
* %b - memory buffer; this takes _two_ arguments: an integer with the
* length of the buffer and a pointer to the buffer.
+ * %c - memory buffer same as %b, but written in constant time; this
+ * takes _three_ arguments: an integer with the length of the buffer,
+ * a pointer to the buffer and the maximum length of the buffer
+ * to avoid potential side channel leaking the buffer length.
* %S - Copy an gcry_sexp_t here. The S-expression needs to be a
* regular one, starting with a parenthesis.
* (no autoswitch to secure allocation)
@@ -1196,7 +1201,7 @@ do_vsexp_sscan (gcry_sexp_t *retsexp, si
{
switch (*p)
{
- case 'b': case 't': case 'v': case 'n': case 'f':
+ case 'b': case 'c': case 't': case 'v': case 'n': case 'f':
case 'r': case '"': case '\'': case '\\':
quoted_esc = 0;
break;
@@ -1469,14 +1474,27 @@ do_vsexp_sscan (gcry_sexp_t *retsexp, si
memcpy (c.pos, astr, alen);
c.pos += alen;
}
- else if (*p == 'b')
+ else if (*p == 'b' || *p == 'c')
{
/* Insert a memory buffer. */
const char *astr;
- int alen;
+ int alen, buflen = 0, reallen = 0;
ARG_NEXT (alen, int);
ARG_NEXT (astr, const char *);
+ if (*p == 'c')
+ {
+ ARG_NEXT (buflen, int);
+ if (buflen < alen)
+ {
+ *erroff = p - buffer;
+ err = GPG_ERR_INV_ARG;
+ goto leave;
+ }
+ /* Do all the calculations with the buflen */
+ reallen = alen;
+ alen = buflen;
+ }
MAKE_SPACE (alen);
if (alen
@@ -1502,9 +1520,18 @@ do_vsexp_sscan (gcry_sexp_t *retsexp, si
}
*c.pos++ = ST_DATA;
- STORE_LEN (c.pos, alen);
- memcpy (c.pos, astr, alen);
- c.pos += alen;
+ if (*p == 'c')
+ {
+ STORE_LEN (c.pos, reallen);
+ ct_memcpy (c.pos, astr, reallen, buflen);
+ c.pos += reallen;
+ }
+ else
+ {
+ STORE_LEN (c.pos, alen);
+ memcpy (c.pos, astr, alen);
+ c.pos += alen;
+ }
}
else if (*p == 'd')
{