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')
 	    {
