[M] Change in openvpn[master]: oob: Answer SERVER_PROBE on the server (P_CONTROL_OOB_V1)

"stipa \(Code Review\) via Openvpn-devel" <[email protected]>
Newsgroups gmane.network.openvpn.devel
Message-ID <afdaa0452b251eb8fe4eb937365a8dee6c510be2-EmailReplacePatchSet-HTML@gerrit.openvpn.net>
Attention is currently required from: flichtenheld, plaisthos.

Hello plaisthos, 

I'd like you to reexamine a change. Please visit

    http://gerrit.openvpn.net/c/openvpn/+/1744?usp=email

to look at the new patch set (#12).


Change subject: oob: Answer SERVER_PROBE on the server (P_CONTROL_OOB_V1)
......................................................................

oob: Answer SERVER_PROBE on the server (P_CONTROL_OOB_V1)

Make a --mode server UDP listener answer an out-of-band SERVER_PROBE without
creating a session, so probing costs the server no state:

  - tls_pre_decrypt_lite() accepts P_CONTROL_OOB_V1 and returns the new
    VERDICT_VALID_OOB_V1 verdict.
  - tls_wrap_oob_standalone() builds a session-less P_CONTROL_OOB_V1 packet:
    opcode + session id + the usual tls-auth/tls-crypt wrapping around a bare
    TLV payload (no reliability/ACK fields), mirroring tls_reset_standalone().
  - oob_client_reply_write() writes the PROBE_REPLY message (message-type
    header + probe_reply TLV).
  - do_pre_decrypt_check() handles the new verdict: it asks
    oob_build_probe_reply() whether to answer, and if so sends a PROBE_REPLY
    via send_probe_reply() (synchronous, stateless, like the HMAC reset path)
    and returns false so no session is created. The verdict joins the reset
    verdicts in the reflect_filter rate-limit check, so a probe flood is capped
    like a reset flood.

The reply carries, as its own session id, the stateless SYN-cookie the three-way
handshake uses, so the server keeps nothing per probe and a client can later
reuse the reply as the server's reset.

Change-Id: I930d3789e0313aa0c3bc51ee5fd1d108343d59f0
Signed-off-by: Lev Stipakov <[email protected]>
---
M src/openvpn/mudp.c
M src/openvpn/oob.c
M src/openvpn/oob.h
M src/openvpn/ssl_pkt.c
M src/openvpn/ssl_pkt.h
5 files changed, 118 insertions(+), 4 deletions(-)


  git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/44/1744/12

diff --git a/src/openvpn/mudp.c b/src/openvpn/mudp.c
index de3d467..01d7441 100644
--- a/src/openvpn/mudp.c
+++ b/src/openvpn/mudp.c
@@ -32,6 +32,7 @@
 
 #include "memdbg.h"
 #include "ssl_pkt.h"
+#include "oob.h"
 
 #ifdef HAVE_SYS_INOTIFY_H
 #include <sys/inotify.h>
@@ -87,6 +88,34 @@
                           "Reset packet from client, sending HMAC based reset challenge", sock);
 }
 
+/* Send an out-of-band PROBE_REPLY back to the source of a SERVER_PROBE,
+ * synchronously and without keeping any state, mirroring the reset path. */
+static void
+send_probe_reply(struct multi_context *m, struct tls_pre_decrypt_state *state,
+                 struct tls_auth_standalone *tas, const struct oob_probe_reply *reply,
+                 struct session_id *own_sid, struct link_socket *sock)
+{
+    struct gc_arena gc = gc_new();
+
+    /* Build the payload of the reply (message-type header + probe_reply TLV) */
+    struct buffer payload = alloc_buf_gc(128, &gc);
+    if (!oob_client_reply_write(&payload, reply))
+    {
+        gc_free(&gc);
+        return;
+    }
+
+    /* OOB replies use the same control-channel wrapping as the request */
+    reset_packet_id_send(&state->tls_wrap_tmp.opt.packet_id.send);
+    state->tls_wrap_tmp.opt.packet_id.rec.initialized = true;
+
+    struct buffer buf = tls_wrap_oob_standalone(&state->tls_wrap_tmp, tas, own_sid, &payload);
+    send_standalone_reply(m, &buf, "Server Probe", "Server probe from client, sending probe reply",
+                          sock);
+
+    gc_free(&gc);
+}
+
 
 /* Returns true if this packet should create a new session */
 static bool
@@ -105,7 +134,8 @@
     const struct openvpn_sockaddr *from = &m->top.c2.from.dest;
     int handwindow = m->top.options.handshake_window;
 
-    if (verdict == VERDICT_VALID_RESET_V3 || verdict == VERDICT_VALID_RESET_V2)
+    if (verdict == VERDICT_VALID_RESET_V3 || verdict == VERDICT_VALID_RESET_V2
+        || verdict == VERDICT_VALID_OOB_V1)
     {
         /* Check if we are still below our limit for sending out
          * responses */
@@ -203,6 +233,31 @@
 
         return ret;
     }
+    else if (verdict == VERDICT_VALID_OOB_V1)
+    {
+        /* Out-of-band server probe. state->newbuf points at the TLV payload
+         * (read_control_auth has stripped the opcode, session id and any
+         * tls-auth/tls-crypt wrapping). Answer it without creating a session. */
+        struct oob_probe_reply reply;
+        if (!oob_build_probe_reply(&state->newbuf, (uint64_t)now, (uint64_t)handwindow,
+                                   &state->peer_session_id, &reply))
+        {
+            /* malformed or replayed/stale probe: silently drop */
+            return false;
+        }
+
+        /* Our session id is a stateless SYN cookie (the same HMAC the three-way
+         * handshake uses): we keep no per-probe state, and the reply can later
+         * also serve as the server's CONTROL_HARD_RESET_SERVER_V2, so a client
+         * can start the handshake from it (the connect_lifetime advertisement). */
+        struct session_id sid =
+            calculate_session_id_hmac(state->peer_session_id, from, hmac_key, handwindow, 0);
+
+        send_probe_reply(m, state, tas, &reply, &sid, sock);
+
+        /* An OOB probe never creates a session */
+        return false;
+    }
 
     /* VERDICT_INVALID */
     return false;
diff --git a/src/openvpn/oob.c b/src/openvpn/oob.c
index 930b213..c841d59 100644
--- a/src/openvpn/oob.c
+++ b/src/openvpn/oob.c
@@ -106,6 +106,12 @@
 }
 
 bool
+oob_client_reply_write(struct buffer *buf, const struct oob_probe_reply *reply)
+{
+    return buf_write_u16(buf, OOB_MSG_PROBE_REPLY) && oob_probe_reply_write(buf, reply);
+}
+
+bool
 oob_timestamp_in_window(uint64_t probe_ts, uint64_t now, uint64_t window_secs)
 {
     uint64_t diff = (now > probe_ts) ? (now - probe_ts) : (probe_ts - now);
diff --git a/src/openvpn/oob.h b/src/openvpn/oob.h
index 1afabbd..49d64e8 100644
--- a/src/openvpn/oob.h
+++ b/src/openvpn/oob.h
@@ -126,6 +126,12 @@
 bool oob_server_probe_read(struct buffer *payload, struct oob_probe_parameter *param);
 
 /**
+ * Write a complete PROBE_REPLY message (message-type header + probe_reply TLV)
+ * to buf. Sent by the server.
+ */
+bool oob_client_reply_write(struct buffer *buf, const struct oob_probe_reply *reply);
+
+/**
  * Check whether a probe timestamp is within an acceptable window around the
  * current time. Used to cheaply drop replayed or implausibly-timed probes
  * before doing any further work (see the probe_parameter timestamp rationale
diff --git a/src/openvpn/ssl_pkt.c b/src/openvpn/ssl_pkt.c
index 39cb648..78552c4 100644
--- a/src/openvpn/ssl_pkt.c
+++ b/src/openvpn/ssl_pkt.c
@@ -314,7 +314,8 @@
 
     /* Allow only the reset packet or the first packet of the actual handshake. */
     if (op != P_CONTROL_HARD_RESET_CLIENT_V2 && op != P_CONTROL_HARD_RESET_CLIENT_V3
-        && op != P_CONTROL_V1 && op != P_CONTROL_WKC_V1 && op != P_ACK_V1)
+        && op != P_CONTROL_V1 && op != P_CONTROL_WKC_V1 && op != P_ACK_V1
+        && !opcode_is_oob(op))
     {
         /*
          * This can occur due to bogus data or DoS packets.
@@ -389,6 +390,10 @@
     {
         return VERDICT_VALID_WKC_V1;
     }
+    else if (opcode_is_oob(op))
+    {
+        return VERDICT_VALID_OOB_V1;
+    }
     else
     {
         return VERDICT_VALID_RESET_V2;
@@ -443,6 +448,29 @@
     return buf;
 }
 
+struct buffer
+tls_wrap_oob_standalone(struct tls_wrap_ctx *ctx, struct tls_auth_standalone *tas,
+                        struct session_id *own_sid, const struct buffer *payload)
+{
+    /* 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));
+
+    /* Out-of-band messages carry the payload directly, with no reliability
+     * or ACK fields. */
+    ASSERT(buf_copy(&buf, payload));
+
+    uint8_t header = (uint8_t)(P_CONTROL_OOB_V1 << P_OPCODE_SHIFT);
+
+    /* Add tls-auth/tls-crypt wrapping, this might replace buf with
+     * ctx->work */
+    tls_wrap_control(ctx, header, &buf, own_sid);
+
+    return buf;
+}
+
 struct session_id
 calculate_session_id_hmac(struct session_id client_sid, const struct openvpn_sockaddr *from,
                           const uint8_t *key, int handwindow, int offset)
diff --git a/src/openvpn/ssl_pkt.h b/src/openvpn/ssl_pkt.h
index d46b8c5..581ec48 100644
--- a/src/openvpn/ssl_pkt.h
+++ b/src/openvpn/ssl_pkt.h
@@ -62,8 +62,9 @@
  * control channel session (e.g. a server probe). Inherently unreliable:
  * there is no protocol-level retransmission.
  *
- * It is not legal on an established session; see opcode_valid_in_session()
- * below. */
+ * On the server this is accepted via the tls_pre_decrypt_lite() allowlist and
+ * handled in the new-connection path (it never creates a session). It is not
+ * legal on an established session; see opcode_valid_in_session() below. */
 #define P_CONTROL_OOB_V1 12
 
 /* define the range of defined opcodes, in- and out-of-band. Note this is not
@@ -136,6 +137,9 @@
     VERDICT_VALID_ACK_V1,
     /** The packet is a valid control packet with appended wrapped client key */
     VERDICT_VALID_WKC_V1,
+    /** This packet is a valid out-of-band control message (e.g. a server
+     * probe). It does not belong to a session and must not create one. */
+    VERDICT_VALID_OOB_V1,
     /** the packet failed on of the various checks */
     VERDICT_INVALID
 };
@@ -256,6 +260,21 @@
                                    struct session_id *own_sid, struct session_id *remote_sid,
                                    uint8_t header, bool request_resend_wkc);
 
+/**
+ * Wrap an already-built out-of-band payload (e.g. probe-reply TLVs) into a
+ * standalone, session-less P_CONTROL_OOB_V1 packet: it prepends the opcode and
+ * own_sid and applies the same tls-auth/tls-crypt wrapping as a regular
+ * control packet, but carries no reliability/ACK fields.
+ *
+ * @param ctx       tls wrapping context (from the pre-decrypt state)
+ * @param tas       standalone auth context providing the work buffer
+ * @param own_sid   session id to use as our session id in the header
+ * @param payload   the OOB message payload (TLV stream) to wrap
+ * @return          the wrapped packet buffer, ready to send
+ */
+struct buffer tls_wrap_oob_standalone(struct tls_wrap_ctx *ctx, struct tls_auth_standalone *tas,
+                                      struct session_id *own_sid, const struct buffer *payload);
+
 
 /**
  * Extracts a control channel message from buf and adjusts the size of

-- 
To view, visit http://gerrit.openvpn.net/c/openvpn/+/1744?usp=email
To unsubscribe, or for help writing mail filters, visit http://gerrit.openvpn.net/settings?usp=email

Gerrit-MessageType: newpatchset
Gerrit-Project: openvpn
Gerrit-Branch: master
Gerrit-Change-Id: I930d3789e0313aa0c3bc51ee5fd1d108343d59f0
Gerrit-Change-Number: 1744
Gerrit-PatchSet: 12
Gerrit-Owner: stipa <[email protected]>
Gerrit-Reviewer: plaisthos <[email protected]>
Gerrit-CC: flichtenheld <[email protected]>
Gerrit-CC: openvpn-devel <[email protected]>
Gerrit-Attention: plaisthos <[email protected]>
Gerrit-Attention: flichtenheld <[email protected]>

_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.