Add output length parameters to mbedtls_gcm_update

Alternative implementations of GCM may delay the output of partial
blocks from mbedtls_gcm_update(). Add an output length parameter to
mbedtls_gcm_update() to allow such implementations to delay the output
of partial blocks. With the software implementation, there is no such
delay.

Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
diff --git a/ChangeLog.d/gcm-update.txt b/ChangeLog.d/gcm-update.txt
index d384551..10d53ef 100644
--- a/ChangeLog.d/gcm-update.txt
+++ b/ChangeLog.d/gcm-update.txt
@@ -2,6 +2,7 @@
    * The interface of the GCM module has changed to remove restrictions on
      how the input to multipart operations is broken down. mbedtls_gcm_finish()
      now takes an extra output parameter for the last partial output block.
+     mbedtls_gcm_update() now takes extra parameters for the output length.
      The software implementation always produces the full output at each
      call to mbedtls_gcm_update(), but alternative implementations activated
      by MBEDTLS_GCM_ALT may delay partial blocks to the next call to
diff --git a/include/mbedtls/gcm.h b/include/mbedtls/gcm.h
index 951ee00..0bd6e1e 100644
--- a/include/mbedtls/gcm.h
+++ b/include/mbedtls/gcm.h
@@ -253,22 +253,42 @@
  *                  input buffer. If the buffers overlap, the output buffer
  *                  must trail at least 8 Bytes behind the input buffer.
  *
- * \param ctx       The GCM context. This must be initialized.
- * \param length    The length of the input data.
- * \param input     The buffer holding the input data. If \p length is greater
- *                  than zero, this must be a readable buffer of at least that
- *                  size in Bytes.
- * \param output    The buffer for holding the output data. If \p length is
- *                  greater than zero, this must be a writable buffer of at
- *                  least that size in Bytes.
+ * \param ctx           The GCM context. This must be initialized.
+ * \param input         The buffer holding the input data. If \p input_length
+ *                      is greater than zero, this must be a readable buffer
+ *                      of at least \p input_length bytes.
+ * \param input_length  The length of the input data in bytes.
+ * \param output        The buffer for the output data. If \p output_length
+ *                      is greater than zero, this must be a writable buffer of
+ *                      of at least \p output_size bytes.
+ *                      This function may withhold the end of the output if
+ *                      it is a partial block for the underlying block cipher.
+ *                      That is, if the cumulated input passed to
+ *                      mbedtls_gcm_update() so far (including the current call)
+ *                      is 16 *n* + *p* with *p* < 16, this function may
+ *                      withhold the last *p* bytes, which will be output by
+ *                      a subsequent call to mbedtls_gcm_update() or
+ *                      mbedtls_gcm_finish().
+ * \param output_size   The size of the output buffer in bytes.
+ *                      This must be at least \p input_length plus the length
+ *                      of the input withheld by the previous call to
+ *                      mbedtls_gcm_update(). Therefore:
+ *                      - With arbitrary inputs, \p output_size may need to
+ *                        be as large as `input_length + 15`.
+ *                      - If all input lengths are a multiple of 16, then
+ *                        \p output_size = \p input_length is sufficient.
+ * \param output_length On success, \p *output_length contains the actual
+ *                      length of the output written in \p output.
+ *                      On failure, the content of \p *output_length is
+ *                      unspecified.
  *
  * \return         \c 0 on success.
  * \return         #MBEDTLS_ERR_GCM_BAD_INPUT on failure.
  */
 int mbedtls_gcm_update( mbedtls_gcm_context *ctx,
-                size_t length,
-                const unsigned char *input,
-                unsigned char *output );
+                        const unsigned char *input, size_t input_length,
+                        unsigned char *output, size_t output_size,
+                        size_t *output_length );
 
 /**
  * \brief           This function finishes the GCM operation and generates
diff --git a/library/cipher.c b/library/cipher.c
index 63eaba8..7e6d0e0 100644
--- a/library/cipher.c
+++ b/library/cipher.c
@@ -545,9 +545,9 @@
 #if defined(MBEDTLS_GCM_C)
     if( ctx->cipher_info->mode == MBEDTLS_MODE_GCM )
     {
-        *olen = ilen;
-        return( mbedtls_gcm_update( (mbedtls_gcm_context *) ctx->cipher_ctx, ilen, input,
-                                    output ) );
+        return( mbedtls_gcm_update( (mbedtls_gcm_context *) ctx->cipher_ctx,
+                                    input, ilen,
+                                    output, ilen, olen ) );
     }
 #endif
 
diff --git a/library/gcm.c b/library/gcm.c
index de766bc..13e7296 100644
--- a/library/gcm.c
+++ b/library/gcm.c
@@ -395,9 +395,9 @@
 }
 
 int mbedtls_gcm_update( mbedtls_gcm_context *ctx,
-                size_t length,
-                const unsigned char *input,
-                unsigned char *output )
+                        const unsigned char *input, size_t input_length,
+                        unsigned char *output, size_t output_size,
+                        size_t *output_length )
 {
     int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
     const unsigned char *p = input;
@@ -405,22 +405,27 @@
     size_t offset;
     unsigned char ectr[16];
 
-    /* Exit early if length==0 so that we don't do any pointer arithmetic on
-     * a potentially null pointer. */
-    if( length == 0 )
+    if( output_size < input_length )
+        return( MBEDTLS_ERR_GCM_BAD_INPUT );
+    GCM_VALIDATE_RET( output_length != NULL );
+    *output_length = input_length;
+
+    /* Exit early if input_length==0 so that we don't do any pointer arithmetic
+     * on a potentially null pointer. */
+    if( input_length == 0 )
         return( 0 );
 
     GCM_VALIDATE_RET( ctx != NULL );
     GCM_VALIDATE_RET( input != NULL );
     GCM_VALIDATE_RET( output != NULL );
 
-    if( output > input && (size_t) ( output - input ) < length )
+    if( output > input && (size_t) ( output - input ) < input_length )
         return( MBEDTLS_ERR_GCM_BAD_INPUT );
 
     /* Total length is restricted to 2^39 - 256 bits, ie 2^36 - 2^5 bytes
      * Also check for possible overflow */
-    if( ctx->len + length < ctx->len ||
-        (uint64_t) ctx->len + length > 0xFFFFFFFE0ull )
+    if( ctx->len + input_length < ctx->len ||
+        (uint64_t) ctx->len + input_length > 0xFFFFFFFE0ull )
     {
         return( MBEDTLS_ERR_GCM_BAD_INPUT );
     }
@@ -429,8 +434,8 @@
     if( offset != 0 )
     {
         size_t use_len = 16 - offset;
-        if( use_len > length )
-            use_len = length;
+        if( use_len > input_length )
+            use_len = input_length;
 
         if( ( ret = gcm_mask( ctx, ectr, offset, use_len, p, out_p ) ) != 0 )
             return( ret );
@@ -439,14 +444,14 @@
             gcm_mult( ctx, ctx->buf, ctx->buf );
 
         ctx->len += use_len;
-        length -= use_len;
+        input_length -= use_len;
         p += use_len;
         out_p += use_len;
     }
 
-    ctx->len += length;
+    ctx->len += input_length;
 
-    while( length >= 16 )
+    while( input_length >= 16 )
     {
         gcm_incr( ctx->y );
         if( ( ret = gcm_mask( ctx, ectr, 0, 16, p, out_p ) ) != 0 )
@@ -454,15 +459,15 @@
 
         gcm_mult( ctx, ctx->buf, ctx->buf );
 
-        length -= 16;
+        input_length -= 16;
         p += 16;
         out_p += 16;
     }
 
-    if( length > 0 )
+    if( input_length > 0 )
     {
         gcm_incr( ctx->y );
-        if( ( ret = gcm_mask( ctx, ectr, 0, length, p, out_p ) ) != 0 )
+        if( ( ret = gcm_mask( ctx, ectr, 0, input_length, p, out_p ) ) != 0 )
             return( ret );
     }
 
@@ -532,6 +537,7 @@
                        unsigned char *tag )
 {
     int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
+    size_t olen;
 
     GCM_VALIDATE_RET( ctx != NULL );
     GCM_VALIDATE_RET( iv != NULL );
@@ -543,7 +549,8 @@
     if( ( ret = mbedtls_gcm_starts( ctx, mode, iv, iv_len, add, add_len ) ) != 0 )
         return( ret );
 
-    if( ( ret = mbedtls_gcm_update( ctx, length, input, output ) ) != 0 )
+    if( ( ret = mbedtls_gcm_update( ctx, input, length,
+                                    output, length, &olen ) ) != 0 )
         return( ret );
 
     if( ( ret = mbedtls_gcm_finish( ctx, NULL, 0, tag, tag_len ) ) != 0 )
@@ -840,6 +847,7 @@
     unsigned char tag_buf[16];
     int i, j, ret;
     mbedtls_cipher_id_t cipher = MBEDTLS_CIPHER_ID_AES;
+    size_t olen;
 
     for( j = 0; j < 3; j++ )
     {
@@ -963,25 +971,34 @@
             if( pt_len_test_data[i] > 32 )
             {
                 size_t rest_len = pt_len_test_data[i] - 32;
-                ret = mbedtls_gcm_update( &ctx, 32,
+                ret = mbedtls_gcm_update( &ctx,
                                           pt_test_data[pt_index_test_data[i]],
-                                          buf );
+                                          32,
+                                          buf, sizeof( buf ), &olen );
                 if( ret != 0 )
                     goto exit;
+                if( olen != 32 )
+                    goto exit;
 
-                ret = mbedtls_gcm_update( &ctx, rest_len,
-                                      pt_test_data[pt_index_test_data[i]] + 32,
-                                      buf + 32 );
+                ret = mbedtls_gcm_update( &ctx,
+                                          pt_test_data[pt_index_test_data[i]] + 32,
+                                          rest_len,
+                                          buf + 32, sizeof( buf ) - 32, &olen );
                 if( ret != 0 )
                     goto exit;
+                if( olen != rest_len )
+                    goto exit;
             }
             else
             {
-                ret = mbedtls_gcm_update( &ctx, pt_len_test_data[i],
+                ret = mbedtls_gcm_update( &ctx,
                                           pt_test_data[pt_index_test_data[i]],
-                                          buf );
+                                          pt_len_test_data[i],
+                                          buf, sizeof( buf ), &olen );
                 if( ret != 0 )
                     goto exit;
+                if( olen != pt_len_test_data[i] )
+                    goto exit;
             }
 
             ret = mbedtls_gcm_finish( &ctx, NULL, 0, tag_buf, 16 );
@@ -1024,24 +1041,33 @@
             if( pt_len_test_data[i] > 32 )
             {
                 size_t rest_len = pt_len_test_data[i] - 32;
-                ret = mbedtls_gcm_update( &ctx, 32, ct_test_data[j * 6 + i],
-                                          buf );
+                ret = mbedtls_gcm_update( &ctx,
+                                          ct_test_data[j * 6 + i], 32,
+                                          buf, sizeof( buf ), &olen );
                 if( ret != 0 )
                     goto exit;
+                if( olen != 32 )
+                    goto exit;
 
-                ret = mbedtls_gcm_update( &ctx, rest_len,
+                ret = mbedtls_gcm_update( &ctx,
                                           ct_test_data[j * 6 + i] + 32,
-                                          buf + 32 );
+                                          rest_len,
+                                          buf + 32, sizeof( buf ) - 32, &olen );
                 if( ret != 0 )
                     goto exit;
+                if( olen != rest_len )
+                    goto exit;
             }
             else
             {
-                ret = mbedtls_gcm_update( &ctx, pt_len_test_data[i],
+                ret = mbedtls_gcm_update( &ctx,
                                           ct_test_data[j * 6 + i],
-                                          buf );
+                                          pt_len_test_data[i],
+                                          buf, sizeof( buf ), &olen );
                 if( ret != 0 )
                     goto exit;
+                if( olen != pt_len_test_data[i] )
+                    goto exit;
             }
 
             ret = mbedtls_gcm_finish( &ctx, NULL, 0, tag_buf, 16 );
diff --git a/tests/suites/test_suite_gcm.function b/tests/suites/test_suite_gcm.function
index 965d154..da6aea8 100644
--- a/tests/suites/test_suite_gcm.function
+++ b/tests/suites/test_suite_gcm.function
@@ -16,6 +16,7 @@
     int ok = 0;
     uint8_t *output = NULL;
     size_t n2 = input->len - n1;
+    size_t olen;
 
     /* Sanity checks on the test data */
     TEST_ASSERT( n1 <= input->len );
@@ -29,14 +30,18 @@
      * tries to write beyond the advertised required buffer size, this will
      * count as an overflow for memory sanitizers and static checkers. */
     ASSERT_ALLOC( output, n1 );
-    TEST_EQUAL( 0, mbedtls_gcm_update( ctx, n1, input->x, output ) );
-    ASSERT_COMPARE( output, n1, expected_output->x, n1 );
+    olen = 0xdeadbeef;
+    TEST_EQUAL( 0, mbedtls_gcm_update( ctx, input->x, n1, output, n1, &olen ) );
+    TEST_EQUAL( n1, olen );
+    ASSERT_COMPARE( output, olen, expected_output->x, n1 );
     mbedtls_free( output );
     output = NULL;
 
     ASSERT_ALLOC( output, n2 );
-    TEST_EQUAL( 0, mbedtls_gcm_update( ctx, n2, input->x + n1, output ) );
-    ASSERT_COMPARE( output, n2, expected_output->x + n1, n2 );
+    olen = 0xdeadbeef;
+    TEST_EQUAL( 0, mbedtls_gcm_update( ctx, input->x + n1, n2, output, n2, &olen ) );
+    TEST_EQUAL( n2, olen );
+    ASSERT_COMPARE( output, olen, expected_output->x + n1, n2 );
     mbedtls_free( output );
     output = NULL;
 
@@ -185,6 +190,7 @@
     int valid_mode = MBEDTLS_GCM_ENCRYPT;
     int valid_len = sizeof(valid_buffer);
     int valid_bitlen = 128, invalid_bitlen = 1;
+    size_t olen;
 
     mbedtls_gcm_init( &ctx );
 
@@ -312,16 +318,20 @@
     /* mbedtls_gcm_update() */
     TEST_INVALID_PARAM_RET(
         MBEDTLS_ERR_GCM_BAD_INPUT,
-        mbedtls_gcm_update( NULL, valid_len,
-                            valid_buffer, valid_buffer ) );
+        mbedtls_gcm_update( NULL, valid_buffer, valid_len,
+                            valid_buffer, valid_len, &olen ) );
     TEST_INVALID_PARAM_RET(
         MBEDTLS_ERR_GCM_BAD_INPUT,
-        mbedtls_gcm_update( &ctx, valid_len,
-                            NULL, valid_buffer ) );
+        mbedtls_gcm_update( &ctx, NULL, valid_len,
+                            valid_buffer, valid_len, &olen ) );
     TEST_INVALID_PARAM_RET(
         MBEDTLS_ERR_GCM_BAD_INPUT,
-        mbedtls_gcm_update( &ctx, valid_len,
-                            valid_buffer, NULL ) );
+        mbedtls_gcm_update( &ctx, valid_buffer, valid_len,
+                            NULL, valid_len, &olen ) );
+    TEST_INVALID_PARAM_RET(
+        MBEDTLS_ERR_GCM_BAD_INPUT,
+        mbedtls_gcm_update( &ctx, valid_buffer, valid_len,
+                            valid_buffer, valid_len, NULL ) );
 
     /* mbedtls_gcm_finish() */
     TEST_INVALID_PARAM_RET(