Remove alignment requirement for mbedtls_gcm_update: implementation

mbedtls_gcm_update now accepts inputs of arbitrary size. There is no
longer a requirement that all calls except the last one pass a
multiple of 16 bytes.

This commit updates the library code and adjusts the GCM tests to
exercise arbitrarily aligned input sizes.

Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
diff --git a/library/gcm.c b/library/gcm.c
index 300521e..b3105d8 100644
--- a/library/gcm.c
+++ b/library/gcm.c
@@ -355,21 +355,64 @@
     return( 0 );
 }
 
+/* Increment the counter. */
+static void gcm_incr( unsigned char y[16] )
+{
+    size_t i;
+    for( i = 16; i > 12; i-- )
+        if( ++y[i - 1] != 0 )
+            break;
+}
+
+/* Calculate and apply the encryption mask. Process use_len bytes of data,
+ * starting at position offset in the mask block. */
+static int gcm_mask( mbedtls_gcm_context *ctx,
+                     unsigned char ectr[16],
+                     size_t offset, size_t use_len,
+                     const unsigned char *input,
+                     unsigned char *output )
+{
+    size_t i;
+    size_t olen = 0;
+    int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
+
+    if( ( ret = mbedtls_cipher_update( &ctx->cipher_ctx, ctx->y, 16, ectr,
+                                       &olen ) ) != 0 )
+    {
+        mbedtls_platform_zeroize( ectr, 16 );
+        return( ret );
+    }
+
+    for( i = 0; i < use_len; i++ )
+    {
+        if( ctx->mode == MBEDTLS_GCM_DECRYPT )
+            ctx->buf[offset + i] ^= input[i];
+        output[i] = ectr[offset + i] ^ input[i];
+        if( ctx->mode == MBEDTLS_GCM_ENCRYPT )
+            ctx->buf[offset + i] ^= output[i];
+    }
+    return( 0 );
+}
+
 int mbedtls_gcm_update( mbedtls_gcm_context *ctx,
                 size_t length,
                 const unsigned char *input,
                 unsigned char *output )
 {
     int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
-    unsigned char ectr[16];
-    size_t i;
-    const unsigned char *p;
+    const unsigned char *p = input;
     unsigned char *out_p = output;
-    size_t use_len, olen = 0;
+    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 )
+        return( 0 );
 
     GCM_VALIDATE_RET( ctx != NULL );
-    GCM_VALIDATE_RET( length == 0 || input != NULL );
-    GCM_VALIDATE_RET( length == 0 || output != NULL );
+    GCM_VALIDATE_RET( input != NULL );
+    GCM_VALIDATE_RET( output != NULL );
 
     if( output > input && (size_t) ( output - input ) < length )
         return( MBEDTLS_ERR_GCM_BAD_INPUT );
@@ -382,39 +425,48 @@
         return( MBEDTLS_ERR_GCM_BAD_INPUT );
     }
 
-    ctx->len += length;
-
-    p = input;
-    while( length > 0 )
+    offset = ctx->len % 16;
+    if( offset != 0 )
     {
-        use_len = ( length < 16 ) ? length : 16;
+        size_t use_len = 16 - offset;
+        if( use_len > length )
+            use_len = length;
 
-        for( i = 16; i > 12; i-- )
-            if( ++ctx->y[i - 1] != 0 )
-                break;
-
-        if( ( ret = mbedtls_cipher_update( &ctx->cipher_ctx, ctx->y, 16, ectr,
-                                   &olen ) ) != 0 )
-        {
+        if( ( ret = gcm_mask( ctx, ectr, offset, use_len, p, out_p ) ) != 0 )
             return( ret );
-        }
 
-        for( i = 0; i < use_len; i++ )
-        {
-            if( ctx->mode == MBEDTLS_GCM_DECRYPT )
-                ctx->buf[i] ^= p[i];
-            out_p[i] = ectr[i] ^ p[i];
-            if( ctx->mode == MBEDTLS_GCM_ENCRYPT )
-                ctx->buf[i] ^= out_p[i];
-        }
+        if( offset + use_len == 16 )
+            gcm_mult( ctx, ctx->buf, ctx->buf );
 
-        gcm_mult( ctx, ctx->buf, ctx->buf );
-
+        ctx->len += use_len;
         length -= use_len;
         p += use_len;
         out_p += use_len;
     }
 
+    ctx->len += length;
+
+    while( length >= 16 )
+    {
+        gcm_incr( ctx->y );
+        if( ( ret = gcm_mask( ctx, ectr, 0, 16, p, out_p ) ) != 0 )
+            return( ret );
+
+        gcm_mult( ctx, ctx->buf, ctx->buf );
+
+        length -= 16;
+        p += 16;
+        out_p += 16;
+    }
+
+    if( length > 0 )
+    {
+        gcm_incr( ctx->y );
+        if( ( ret = gcm_mask( ctx, ectr, 0, length, p, out_p ) ) != 0 )
+            return( ret );
+    }
+
+    mbedtls_platform_zeroize( ectr, sizeof( ectr ) );
     return( 0 );
 }
 
@@ -436,6 +488,9 @@
     if( tag_len > 16 || tag_len < 4 )
         return( MBEDTLS_ERR_GCM_BAD_INPUT );
 
+    if( ctx->len % 16 != 0 )
+        gcm_mult( ctx, ctx->buf, ctx->buf );
+
     memcpy( tag, ctx->base_ectr, tag_len );
 
     if( orig_len || orig_add_len )
diff --git a/tests/suites/test_suite_gcm.function b/tests/suites/test_suite_gcm.function
index c2f3534..d4dce93 100644
--- a/tests/suites/test_suite_gcm.function
+++ b/tests/suites/test_suite_gcm.function
@@ -111,7 +111,7 @@
         ASSERT_COMPARE( output, src_str->len, dst->x, dst->len );
         ASSERT_COMPARE( tag_output, tag_len, tag->x, tag->len );
 
-        for( n1 = 16; n1 < src_str->len; n1 += 16 )
+        for( n1 = 0; n1 <= src_str->len; n1 += 1 )
         {
             mbedtls_test_set_step( n1 );
             if( !check_multipart( &ctx, MBEDTLS_GCM_ENCRYPT,
@@ -159,7 +159,7 @@
             TEST_ASSERT( ret == 0 );
             ASSERT_COMPARE( output, src_str->len, pt_result->x, pt_result->len );
 
-            for( n1 = 16; n1 < src_str->len; n1 += 16 )
+            for( n1 = 0; n1 <= src_str->len; n1 += 1 )
             {
                 mbedtls_test_set_step( n1 );
                 if( !check_multipart( &ctx, MBEDTLS_GCM_DECRYPT,