diff -Naurp openvpn-2.3.8.orig/src/openvpn/buffer.c openvpn-2.3.8/src/openvpn/buffer.c
--- openvpn-2.3.8.orig/src/openvpn/buffer.c	2025-03-25 10:01:43.746378857 +0100
+++ openvpn-2.3.8/src/openvpn/buffer.c	2025-03-25 13:50:31.577818004 +0100
@@ -858,6 +858,24 @@ string_mod (char *str, const unsigned in
   return ret;
 }
 
+
+bool
+string_check_buf(struct buffer *buf, const unsigned int inclusive, const unsigned int exclusive)
+{
+    int i=0;
+    ASSERT(buf);
+    for (i = 0; i < BLEN(buf); i++)
+    {
+        char c = BSTR(buf)[i];
+        if (!char_inc_exc(c, inclusive, exclusive))
+        {
+            return false;
+        }
+    }
+    return true;
+}
+
+
 const char *
 string_mod_const (const char *str,
 		  const unsigned int inclusive,
diff -Naurp openvpn-2.3.8.orig/src/openvpn/buffer.h openvpn-2.3.8/src/openvpn/buffer.h
--- openvpn-2.3.8.orig/src/openvpn/buffer.h	2025-03-25 10:01:43.745378842 +0100
+++ openvpn-2.3.8/src/openvpn/buffer.h	2025-03-25 10:04:36.000943600 +0100
@@ -750,7 +750,16 @@ const char *np (const char *str);
 bool char_class (const unsigned char c, const unsigned int flags);
 bool string_class (const char *str, const unsigned int inclusive, const unsigned int exclusive);
 bool string_mod (char *str, const unsigned int inclusive, const unsigned int exclusive, const char replace);
-
+/**
+ * Check a buffer if it only consists of allowed characters.
+ *
+ * @param buf The buffer to be checked.
+ * @param inclusive The character classes that are allowed.
+ * @param exclusive Character classes that are not allowed even if they are also in inclusive.
+ * @return True if the string consists only of allowed characters, false otherwise.
+ */
+bool
+string_check_buf(struct buffer *buf, const unsigned int inclusive, const unsigned int exclusive);
 const char *string_mod_const (const char *str,
 			      const unsigned int inclusive,
 			      const unsigned int exclusive,
diff -Naurp openvpn-2.3.8.orig/src/openvpn/forward.c openvpn-2.3.8/src/openvpn/forward.c
--- openvpn-2.3.8.orig/src/openvpn/forward.c	2025-03-25 10:01:43.747378872 +0100
+++ openvpn-2.3.8/src/openvpn/forward.c	2025-03-25 14:29:24.310695980 +0100
@@ -77,6 +77,31 @@ show_wait_status (struct context *c)
 
 #endif
 
+static void
+parse_incoming_control_channel_command(struct context *c, struct buffer *buf)
+{
+    if (buf_string_match_head_str(buf, "AUTH_FAILED"))
+    {
+       receive_auth_failed(c, buf);
+    }
+    else if (buf_string_match_head_str(buf, "PUSH_"))
+    {
+       incoming_push_message(c, buf);
+    }
+    else if (buf_string_match_head_str(buf, "RESTART"))
+    {
+       server_pushed_signal(c, buf, true, 7);
+    }
+    else if (buf_string_match_head_str(buf, "HALT"))
+    {
+       server_pushed_signal(c, buf, false, 4);
+    }
+    else
+    {
+       msg(D_PUSH_ERRORS, "WARNING: Received unknown control message: %s", BSTR(buf));
+    }
+}
+
 /*
  * In TLS mode, let TLS level respond to any control-channel
  * packets which were received, or prepare any packets for
@@ -150,24 +175,46 @@ check_incoming_control_channel_dowork (s
       struct gc_arena gc = gc_new ();
       struct buffer buf = alloc_buf_gc (len, &gc);
       if (tls_rec_payload (c->c2.tls_multi, &buf))
-	{
+      {
 	  /* force null termination of message */
 	  buf_null_terminate (&buf);
 
 	  /* enforce character class restrictions */
 	  string_mod (BSTR (&buf), CC_PRINT, CC_CRLF, 0);
 
-	  if (buf_string_match_head_str (&buf, "AUTH_FAILED"))
-	    receive_auth_failed (c, &buf);
-	  else if (buf_string_match_head_str (&buf, "PUSH_"))
-	    incoming_push_message (c, &buf);
-	  else if (buf_string_match_head_str (&buf, "RESTART"))
-	    server_pushed_signal (c, &buf, true, 7);
-	  else if (buf_string_match_head_str (&buf, "HALT"))
-	    server_pushed_signal (c, &buf, false, 4);
-	  else
-	    msg (D_PUSH_ERRORS, "WARNING: Received unknown control message: %s", BSTR (&buf));
-	}
+	  while (BLEN(&buf) > 1)
+	  {
+                         /* commands on the control channel are seperated by 0x00 bytes.
+             * cmdlen does not include the 0 byte of the string */
+            int cmdlen = (int)strnlen(BSTR(&buf), BLEN(&buf));
+            if (cmdlen < BLEN(&buf))
+            {
+                /* include the NUL byte and ensure NUL termination */
+                int cmdlen = (int)strlen(BSTR(&buf)) + 1;
+                /* Construct a buffer that only holds the current command and
+                 * its closing NUL byte */
+                struct buffer cmdbuf = alloc_buf_gc(cmdlen, &gc);
+                buf_write(&cmdbuf, BPTR(&buf), cmdlen);
+                /* check we have only printable characters or null byte in the
+                 * command string and no newlines */
+                if (!string_check_buf(&buf, CC_PRINT | CC_NULL, CC_CRLF))
+                {
+                    msg(D_PUSH_ERRORS, "WARNING: Received control with invalid characters: %s",
+                        format_hex(BPTR(&buf), BLEN(&buf), 256, &gc));
+                }
+                else
+                {
+                    parse_incoming_control_channel_command(c, &cmdbuf);
+                }
+            }
+            else
+            {
+                msg(D_PUSH_ERRORS, "WARNING: Ignoring control channel "
+                    "message command without NUL termination");
+            }
+            buf_advance(&buf, cmdlen);
+	  }
+      }
       else
 	{
 	  msg (D_PUSH_ERRORS, "WARNING: Receive control message failed");
