[Openvpn-devel,v2] Fix memory leaks in HMAC initial packet id

Message ID 20230315150420.298988-1-arne@rfc2549.org
State Changes Requested
Headers show
Series [Openvpn-devel,v2] Fix memory leaks in HMAC initial packet id | expand

Commit Message

Arne Schwabe March 15, 2023, 3:04 p.m. UTC
The HMAC leaks are just forgotten frees/deinitialisations. tls_wrap_control
will sometimes return the original buffer (non tls-crypt) and sometimes
tls_wrap.work, handling this buffer lifetime is a bit more complicated. Instead
of further complicating that code just give our work buffer the same lifetime
as the other one inside tls_wrap.work as that is also more consistent.

Also packet_id_init will allocated a buffer with malloc and not using a
gc_arena, so we need to manually also free it.

Patch v2: add missing deallocations in unit tests of the new workbuf

Found-By: clang with asan
Signed-off-by: Arne Schwabe <arne@rfc2549.org>
---
 src/openvpn/init.c                  |  3 +++
 src/openvpn/ssl.c                   | 12 +++++++++++
 src/openvpn/ssl.h                   |  6 ++++++
 src/openvpn/ssl_pkt.c               |  8 +++++--
 src/openvpn/ssl_pkt.h               |  2 +-
 tests/unit_tests/openvpn/test_pkt.c | 33 +++++++++++++++++++----------
 6 files changed, 50 insertions(+), 14 deletions(-)

Comments

Gert Doering March 15, 2023, 6:43 p.m. UTC | #1
Hi,

On Wed, Mar 15, 2023 at 04:04:20PM +0100, Arne Schwabe wrote:
> The HMAC leaks are just forgotten frees/deinitialisations. tls_wrap_control
> will sometimes return the original buffer (non tls-crypt) and sometimes
> tls_wrap.work, handling this buffer lifetime is a bit more complicated. Instead
> of further complicating that code just give our work buffer the same lifetime
> as the other one inside tls_wrap.work as that is also more consistent.

This test passes all I've thrown at it, except stare-at-code...

Maybe I am misreading it, but want to make sure.

> diff --git a/src/openvpn/init.c b/src/openvpn/init.c
> index 124ac76bd..fa2681dc7 100644
> --- a/src/openvpn/init.c
> +++ b/src/openvpn/init.c
> @@ -3483,6 +3483,7 @@ do_init_frame_tls(struct context *c)
>          frame_print(&c->c2.tls_auth_standalone->frame, D_MTU_INFO,
>                      "TLS-Auth MTU parms");
>          c->c2.tls_auth_standalone->tls_wrap.work = alloc_buf_gc(BUF_SIZE(&c->c2.frame), &c->c2.gc);
> +        c->c2.tls_auth_standalone->workbuf = alloc_buf_gc(BUF_SIZE(&c->c2.frame), &c->c2.gc);
>      }

At this place, we have a tls_auth_standalone() (!= NULL) already, which
got initialized by tls_auth_standalone_init().

We allocate tas->workbuf here.

> diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c
> index 78cec90a1..f2331f712 100644
> --- a/src/openvpn/ssl.c
> +++ b/src/openvpn/ssl.c
> @@ -1358,9 +1358,21 @@ tls_auth_standalone_init(struct tls_options *tls_options,
>      packet_id_init(&tas->tls_wrap.opt.packet_id, tls_options->replay_window,
>                     tls_options->replay_time, "TAS", 0);
>  
> +    tas->workbuf = alloc_buf_gc(tas->frame.buf.payload_size, gc);
>      return tas;
>  }

This is tls_auth_standalone_init(), which has already allocated
a tas->workbuf.

So my reading is "we allocate this twice" - which is not a super big
problem because it's both in the gc_arena and will be freed on session
exit (mmmh, which session, anyway?) - but it's still "too many bufs",
plus "readers of that code will get confused".

gert

Patch

diff --git a/src/openvpn/init.c b/src/openvpn/init.c
index 124ac76bd..fa2681dc7 100644
--- a/src/openvpn/init.c
+++ b/src/openvpn/init.c
@@ -3483,6 +3483,7 @@  do_init_frame_tls(struct context *c)
         frame_print(&c->c2.tls_auth_standalone->frame, D_MTU_INFO,
                     "TLS-Auth MTU parms");
         c->c2.tls_auth_standalone->tls_wrap.work = alloc_buf_gc(BUF_SIZE(&c->c2.frame), &c->c2.gc);
+        c->c2.tls_auth_standalone->workbuf = alloc_buf_gc(BUF_SIZE(&c->c2.frame), &c->c2.gc);
     }
 }
 
@@ -3881,6 +3882,8 @@  do_close_tls(struct context *c)
         md_ctx_cleanup(c->c2.pulled_options_state);
         md_ctx_free(c->c2.pulled_options_state);
     }
+
+    tls_auth_standalone_free(c->c2.tls_auth_standalone);
 }
 
 /*
diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c
index 78cec90a1..f2331f712 100644
--- a/src/openvpn/ssl.c
+++ b/src/openvpn/ssl.c
@@ -1358,9 +1358,21 @@  tls_auth_standalone_init(struct tls_options *tls_options,
     packet_id_init(&tas->tls_wrap.opt.packet_id, tls_options->replay_window,
                    tls_options->replay_time, "TAS", 0);
 
+    tas->workbuf = alloc_buf_gc(tas->frame.buf.payload_size, gc);
     return tas;
 }
 
+void
+tls_auth_standalone_free(struct tls_auth_standalone *tas)
+{
+    if (!tas)
+    {
+        return;
+    }
+
+    packet_id_free(&tas->tls_wrap.opt.packet_id);
+}
+
 /*
  * Set local and remote option compatibility strings.
  * Used to verify compatibility of local and remote option
diff --git a/src/openvpn/ssl.h b/src/openvpn/ssl.h
index 58ff4b9b4..a050cd5c9 100644
--- a/src/openvpn/ssl.h
+++ b/src/openvpn/ssl.h
@@ -180,6 +180,12 @@  void tls_multi_init_finalize(struct tls_multi *multi, int tls_mtu);
 struct tls_auth_standalone *tls_auth_standalone_init(struct tls_options *tls_options,
                                                      struct gc_arena *gc);
 
+/**
+ * Frees a standalone tls-auth verification object.
+ * @param tas   the object to free. May be NULL.
+ */
+void tls_auth_standalone_free(struct tls_auth_standalone *tas);
+
 /*
  * Setups the control channel frame size parameters from the data channel
  * parameters
diff --git a/src/openvpn/ssl_pkt.c b/src/openvpn/ssl_pkt.c
index 17a7f8917..8b3391e76 100644
--- a/src/openvpn/ssl_pkt.c
+++ b/src/openvpn/ssl_pkt.c
@@ -434,7 +434,10 @@  tls_reset_standalone(struct tls_wrap_ctx *ctx,
                      uint8_t header,
                      bool request_resend_wkc)
 {
-    struct buffer buf = alloc_buf(tas->frame.buf.payload_size);
+    /* Copy buffer here to point at the same data but allow tls_wrap_control
+     * to potentially change buf to point to another buffer without
+     * modifying the buffer in tas */
+    struct buffer buf = tas->workbuf;
     ASSERT(buf_init(&buf, tas->frame.buf.headroom));
 
     /* Reliable ACK structure */
@@ -461,7 +464,8 @@  tls_reset_standalone(struct tls_wrap_ctx *ctx,
         buf_write_u16(&buf, EARLY_NEG_FLAG_RESEND_WKC);
     }
 
-    /* Add tls-auth/tls-crypt wrapping, this might replace buf */
+    /* Add tls-auth/tls-crypt wrapping, this might replace buf with
+     * ctx->work */
     tls_wrap_control(ctx, header, &buf, own_sid);
 
     return buf;
diff --git a/src/openvpn/ssl_pkt.h b/src/openvpn/ssl_pkt.h
index ec7b48daf..ef4852e5d 100644
--- a/src/openvpn/ssl_pkt.h
+++ b/src/openvpn/ssl_pkt.h
@@ -77,6 +77,7 @@ 
 struct tls_auth_standalone
 {
     struct tls_wrap_ctx tls_wrap;
+    struct buffer workbuf;
     struct frame frame;
 };
 
@@ -220,7 +221,6 @@  read_control_auth(struct buffer *buf,
  * This function creates a reset packet using the information
  * from the tls pre decrypt state.
  *
- * The returned buf needs to be free with \c free_buf
  */
 struct buffer
 tls_reset_standalone(struct tls_wrap_ctx *ctx,
diff --git a/tests/unit_tests/openvpn/test_pkt.c b/tests/unit_tests/openvpn/test_pkt.c
index f11e52a11..736f13178 100644
--- a/tests/unit_tests/openvpn/test_pkt.c
+++ b/tests/unit_tests/openvpn/test_pkt.c
@@ -203,6 +203,7 @@  init_tas_auth(int key_direction)
                             static_key, true, key_direction,
                             "Control Channel Authentication", "tls-auth",
                             NULL);
+    tas.workbuf = alloc_buf(1600);
 
     return tas;
 }
@@ -217,10 +218,22 @@  init_tas_crypt(bool server)
     tls_crypt_init_key(&tas.tls_wrap.opt.key_ctx_bi,
                        &tas.tls_wrap.original_wrap_keydata, static_key,
                        true, server);
+    tas.workbuf = alloc_buf(1600);
+    tas.tls_wrap.work = alloc_buf(1600);
 
     return tas;
 }
 
+void
+free_tas(struct tls_auth_standalone *tas)
+{
+    /* Not some of these might be null pointers but calling free on null
+     * pointers is a noop */
+    free_key_ctx_bi(&tas->tls_wrap.opt.key_ctx_bi);
+    free_buf(&tas->workbuf);
+    free_buf(&tas->tls_wrap.work);
+}
+
 void
 test_tls_decrypt_lite_crypt(void **ut_state)
 {
@@ -228,7 +241,6 @@  test_tls_decrypt_lite_crypt(void **ut_state)
     struct tls_pre_decrypt_state state = { 0 };
 
     struct tls_auth_standalone tas = init_tas_crypt(true);
-
     struct buffer buf = alloc_buf(1024);
 
     /* tls-auth should be invalid */
@@ -263,6 +275,7 @@  test_tls_decrypt_lite_crypt(void **ut_state)
     }
 
     free_key_ctx_bi(&tas.tls_wrap.opt.key_ctx_bi);
+    free_tas(&tas);
     free_buf(&buf);
 }
 
@@ -318,6 +331,7 @@  test_tls_decrypt_lite_auth(void **ut_state)
     free_tls_pre_decrypt_state(&state);
     /* Wrong key direction gives a wrong hmac key and should not validate */
     free_key_ctx_bi(&tas.tls_wrap.opt.key_ctx_bi);
+    free_tas(&tas);
     tas = init_tas_auth(KEY_DIRECTION_INVERSE);
 
     buf_reset_len(&buf);
@@ -326,7 +340,7 @@  test_tls_decrypt_lite_auth(void **ut_state)
     assert_int_equal(verdict, VERDICT_INVALID);
 
     free_tls_pre_decrypt_state(&state);
-    free_key_ctx_bi(&tas.tls_wrap.opt.key_ctx_bi);
+    free_tas(&tas);
     free_buf(&buf);
 }
 
@@ -371,6 +385,7 @@  test_tls_decrypt_lite_none(void **ut_state)
 
     free_tls_pre_decrypt_state(&state);
     free_buf(&buf);
+    free_tas(&tas);
 }
 
 static void
@@ -442,10 +457,9 @@  test_verify_hmac_tls_auth(void **ut_state)
     bool valid = check_session_id_hmac(&state, &from.dest, hmac, 30);
     assert_false(valid);
 
-    free_key_ctx_bi(&tas.tls_wrap.opt.key_ctx_bi);
-    free_key_ctx(&tas.tls_wrap.tls_crypt_v2_server_key);
     free_tls_pre_decrypt_state(&state);
     free_buf(&buf);
+    free_tas(&tas);
     hmac_ctx_cleanup(hmac);
     hmac_ctx_free(hmac);
 }
@@ -563,6 +577,7 @@  test_generate_reset_packet_plain(void **ut_state)
     tas.tls_wrap.mode = TLS_WRAP_NONE;
     struct frame frame = { {.headroom = 200, .payload_size = 1400}, 0};
     tas.frame = frame;
+    tas.workbuf = alloc_buf(1600);
 
     uint8_t header = 0 | (P_CONTROL_HARD_RESET_CLIENT_V2 << P_OPCODE_SHIFT);
 
@@ -577,10 +592,9 @@  test_generate_reset_packet_plain(void **ut_state)
     struct buffer buf2 = tls_reset_standalone(&tas.tls_wrap, &tas, &client_id, &server_id, header, false);
     assert_int_equal(BLEN(&buf), BLEN(&buf2));
     assert_memory_equal(BPTR(&buf), BPTR(&buf2), BLEN(&buf));
-    free_buf(&buf2);
 
     free_tls_pre_decrypt_state(&state);
-    free_buf(&buf);
+    free_buf(&tas.workbuf);
 }
 
 static void
@@ -614,15 +628,12 @@  test_generate_reset_packet_tls_auth(void **ut_state)
     assert_int_equal(BLEN(&buf), BLEN(&buf2));
     assert_memory_equal(BPTR(&buf), BPTR(&buf2), BLEN(&buf));
 
-    free_buf(&buf2);
     free_tls_pre_decrypt_state(&state);
 
     packet_id_free(&tas_client.tls_wrap.opt.packet_id);
 
-    free_buf(&buf);
-    free_key_ctx_bi(&tas_server.tls_wrap.opt.key_ctx_bi);
-    free_key_ctx_bi(&tas_client.tls_wrap.opt.key_ctx_bi);
-
+    free_tas(&tas_client);
+    free_tas(&tas_server);
 }
 
 int