dcerpc: developer option to save ndr_fuzz_X seeds
authorDouglas Bagnall <douglas.bagnall@catalyst.net.nz>
Wed, 6 Nov 2019 04:27:08 +0000 (17:27 +1300)
committerAndrew Bartlett <abartlet@samba.org>
Tue, 10 Dec 2019 07:50:28 +0000 (07:50 +0000)
Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Signed-off-by: Andrew Bartlett <abartlet@samba.org>
Pair-programmed-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
librpc/rpc/dcerpc_util.c
librpc/rpc/dcesrv_core.c
librpc/rpc/dcesrv_core.h
librpc/rpc/dcesrv_reply.c
librpc/rpc/rpc_common.h
librpc/wscript_build
selftest/target/Samba3.pm
source3/rpc_server/srv_pipe.c

index 7e2d06e0cef5133c1243d0e93136bb3396c7aefd..f7596cb1ac11fb8db59e124da3f22297b886551c 100644 (file)
@@ -29,6 +29,9 @@
 #include "rpc_common.h"
 #include "lib/util/bitmap.h"
 #include "auth/gensec/gensec.h"
+#include "lib/util/mkdir_p.h"
+#include "lib/crypto/gnutls_helpers.h"
+#include <gnutls/crypto.h>
 
 /* we need to be able to get/set the fragment length without doing a full
    decode */
@@ -1447,3 +1450,129 @@ void dcerpc_log_packet(const char *packet_log_dir,
                free(name);
        }
 }
+
+
+#ifdef DEVELOPER
+
+/*
+ * Save valid, well-formed DCE/RPC stubs to use as a seed for
+ * ndr_fuzz_X
+ */
+void dcerpc_save_ndr_fuzz_seed(TALLOC_CTX *mem_ctx,
+                              DATA_BLOB raw_blob,
+                              const char *dump_dir,
+                              const char *iface_name,
+                              int flags,
+                              int opnum,
+                              bool ndr64)
+{
+       char *fname = NULL;
+       const char *sub_dir = NULL;
+       TALLOC_CTX *temp_ctx = talloc_new(mem_ctx);
+       DATA_BLOB blob;
+       int ret, rc;
+       uint8_t digest[20];
+       DATA_BLOB digest_blob;
+       char *digest_hex;
+       uint16_t fuzz_flags = 0;
+
+       /*
+        * We want to save the 'stub' in a per-pipe subdirectory, with
+        * the ndr_fuzz_X header 4 byte header. For the sake of
+        * convenience (this is a developer only function), we mkdir
+        * -p the sub-directories when they are needed.
+        */
+
+       if (dump_dir == NULL) {
+               return;
+       }
+
+       temp_ctx = talloc_stackframe();
+
+       sub_dir = talloc_asprintf(temp_ctx, "%s/%s",
+                                 dump_dir,
+                                 iface_name);
+       if (sub_dir == NULL) {
+               talloc_free(temp_ctx);
+               return;
+       }
+       ret = mkdir_p(sub_dir, 0755);
+       if (ret && errno != EEXIST) {
+               DBG_ERR("could not create %s\n", sub_dir);
+               talloc_free(temp_ctx);
+               return;
+       }
+
+       blob.length = raw_blob.length + 4;
+       blob.data = talloc_array(sub_dir,
+                                uint8_t,
+                                blob.length);
+       if (blob.data == NULL) {
+               DBG_ERR("could not allocate for fuzz seeds! (%s)\n",
+                       iface_name);
+               talloc_free(temp_ctx);
+               return;
+       }
+
+       if (ndr64) {
+               fuzz_flags = 4;
+       }
+       if (flags & NDR_IN) {
+               fuzz_flags |= 1;
+       } else if (flags & NDR_OUT) {
+               fuzz_flags |= 2;
+       }
+
+       SSVAL(blob.data, 0, fuzz_flags);
+       SSVAL(blob.data, 2, opnum);
+
+       memcpy(&blob.data[4],
+              raw_blob.data,
+              raw_blob.length);
+
+       /*
+        * This matches how oss-fuzz names the corpus input files, due
+        * to a preference from libFuzzer
+        */
+       rc = gnutls_hash_fast(GNUTLS_DIG_SHA1,
+                             blob.data,
+                             blob.length,
+                             digest);
+       if (rc < 0) {
+               /*
+                * This prints a better error message, eg if SHA1 is
+                * disabled
+                */
+               NTSTATUS status = gnutls_error_to_ntstatus(rc,
+                                                 NT_STATUS_HASH_NOT_SUPPORTED);
+               DBG_ERR("Failed to generate SHA1 to save fuzz seed: %s",
+                       nt_errstr(status));
+               talloc_free(temp_ctx);
+               return;
+       }
+
+       digest_blob.data = digest;
+       digest_blob.length = sizeof(digest);
+       digest_hex = data_blob_hex_string_lower(temp_ctx, &digest_blob);
+
+       fname = talloc_asprintf(temp_ctx, "%s/%s",
+                               sub_dir,
+                               digest_hex);
+       if (fname == NULL) {
+               talloc_free(temp_ctx);
+               return;
+       }
+
+       /*
+        * If this fails, it is most likely because that file already
+        * exists.  This is fine, it means we already have this
+        * sample
+        */
+       file_save(fname,
+                 blob.data,
+                 blob.length);
+
+       talloc_free(temp_ctx);
+}
+
+#endif /*if DEVELOPER, enveloping _dcesrv_save_ndr_fuzz_seed() */
index b7360abfcf79b71f8698c0fcb1f0e18f9d8d5d11..daa95afcab76a534dc6fa521e5bbcbe68d6357d1 100644 (file)
@@ -33,6 +33,7 @@
 #include "librpc/gen_ndr/ndr_dcerpc.h"
 #include "lib/util/tevent_ntstatus.h"
 
+
 #undef DBGC_CLASS
 #define DBGC_CLASS DBGC_RPC_SRV
 
@@ -1705,6 +1706,32 @@ static void dcesrv_save_call(struct dcesrv_call_state *call, const char *why)
 #endif
 }
 
+#ifdef DEVELOPER
+/*
+  Save the call for use as a seed for fuzzing.
+
+  This is only enabled in a developer build, and only has effect if the
+  "dcesrv fuzz directory" param is set.
+*/
+void _dcesrv_save_ndr_fuzz_seed(DATA_BLOB call_blob,
+                               struct dcesrv_call_state *call,
+                               int flags)
+{
+       const char *dump_dir = lpcfg_parm_string(call->conn->dce_ctx->lp_ctx,
+                                                NULL,
+                                                "dcesrv", "fuzz directory");
+
+       dcerpc_save_ndr_fuzz_seed(call,
+                                 call_blob,
+                                 dump_dir,
+                                 call->context->iface->name,
+                                 flags,
+                                 call->pkt.u.request.opnum,
+                                 call->ndr_pull->flags & LIBNDR_FLAG_NDR64);
+}
+#endif /*if DEVELOPER, enveloping _dcesrv_save_ndr_fuzz_seed() */
+
+
 static NTSTATUS dcesrv_check_verification_trailer(struct dcesrv_call_state *call)
 {
        TALLOC_CTX *frame = talloc_stackframe();
@@ -1848,9 +1875,14 @@ static NTSTATUS dcesrv_request(struct dcesrv_call_state *call)
                } else {
                        dcesrv_save_call(call, "pullfail");
                }
+
                return dcesrv_fault_with_flags(call, call->fault_code, extra_flags);
        }
 
+       dcesrv_save_ndr_fuzz_seed(call->pkt.u.request.stub_and_verifier,
+                                 call,
+                                 NDR_IN);
+
        if (pull->offset != pull->data_size) {
                dcesrv_save_call(call, "extrabytes");
                DEBUG(3,("Warning: %d extra bytes in incoming RPC request\n",
index fc2651bd9d3ba61e306d13be3575499cc8a0b4b7..5e47976ced9595cfe646f79042446d20f3438e4b 100644 (file)
@@ -605,4 +605,18 @@ _PUBLIC_ void dcesrv_sock_report_output_data(struct dcesrv_connection *dce_conn)
 
 _PUBLIC_ NTSTATUS dcesrv_connection_loop_start(struct dcesrv_connection *conn);
 
+
+void _dcesrv_save_ndr_fuzz_seed(DATA_BLOB call_blob,
+                               struct dcesrv_call_state *call,
+                               int flags);
+
+#if DEVELOPER
+#define  dcesrv_save_ndr_fuzz_seed(stub, call, flags) \
+       _dcesrv_save_ndr_fuzz_seed(stub, call, flags)
+#else
+#define  dcesrv_save_ndr_fuzz_seed(stub, call, flags) \
+        /* */
+#endif
+
+
 #endif /* _LIBRPC_RPC_DCESRV_CORE_H_ */
index a217d5a56107ca4ac2d8f7530ee2fea61c13b86e..96bd98f53e10b0c4aa20303d13227470712b5f6b 100644 (file)
@@ -174,6 +174,10 @@ _PUBLIC_ NTSTATUS dcesrv_reply(struct dcesrv_call_state *call)
 
        stub = ndr_push_blob(push);
 
+       dcesrv_save_ndr_fuzz_seed(stub,
+                                 call,
+                                 NDR_OUT);
+
        total_length = stub.length;
 
        /* we can write a full max_recv_frag size, minus the dcerpc
index 3e98c205fcfa44384dab1a22eb1eabf1eb030027..a606a29ed317acbff11a828fc05ccf5744b0a3e1 100644 (file)
@@ -458,4 +458,25 @@ void dcerpc_log_packet(const char *packet_log_dir,
                       const DATA_BLOB *pkt,
                       const char *why);
 
+#ifdef DEVELOPER
+void dcerpc_save_ndr_fuzz_seed(TALLOC_CTX *mem_ctx,
+                              DATA_BLOB raw_blob,
+                              const char *dump_dir,
+                              const char *iface_name,
+                              int flags,
+                              int opnum,
+                              bool ndr64);
+#else
+static inline void dcerpc_save_ndr_fuzz_seed(TALLOC_CTX *mem_ctx,
+                                            DATA_BLOB raw_blob,
+                                            const char *dump_dir,
+                                            const char *iface_name,
+                                            int flags,
+                                            int opnum,
+                                            bool ndr64)
+{
+       return;
+}
+#endif
+
 #endif /* __DEFAULT_LIBRPC_RPCCOMMON_H__ */
index 88964ed2a9ad5d58fb3604bd45ada70bb67bfdde..b9e62859f5d3a6857fcc2f4793feb3778ed9a5ce 100644 (file)
@@ -639,7 +639,7 @@ bld.SAMBA_LIBRARY('dcerpc-server-core',
            rpc/dcesrv_mgmt.c
            rpc/dcesrv_reply.c
            ''',
-    deps='ndr dcerpc-binding',
+    deps='ndr dcerpc-binding samba-util-core gnutls GNUTLS_HELPERS',
     pc_files=[],
     public_headers='rpc/dcesrv_core.h',
     autoproto='rpc/dcesrv_core_proto.h',
index 41d439ea91a6637499509672f45b06ad9df32e32..cdbbbdcef3d7214b09b58ff286e56f0d11f07be6 100755 (executable)
@@ -1436,6 +1436,9 @@ sub provision($$$$$$$$$)
        my $privatedir="$prefix_abs/private";
        push(@dirs,$privatedir);
 
+       my $cachedir = "$prefix_abs/cachedir";
+       push(@dirs, $cachedir);
+
        my $binddnsdir = "$prefix_abs/bind-dns";
        push(@dirs, $binddnsdir);
 
@@ -1702,6 +1705,7 @@ sub provision($$$$$$$$$)
 
        print CONF "
 [global]
+        dcesrv:fuzz directory = $cachedir/fuzz
        netbios name = $server
        interfaces = $interfaces
        bind interfaces only = yes
index fa5043e8c1fe19c1c8d6eddceeb0d1789ec10ee9..3bc291fb0c98ad3038a4e6e1c29d447b25f512cf 100644 (file)
@@ -1401,6 +1401,7 @@ static bool api_pipe_request(struct pipes_struct *p,
        bool ret = False;
        struct pipe_rpc_fns *pipe_fns;
        const char *interface_name = NULL;
+       const char *dump_dir = NULL;
 
        if (!p->pipe_bound) {
                DEBUG(1, ("Pipe not bound!\n"));
@@ -1465,6 +1466,16 @@ static bool api_pipe_request(struct pipes_struct *p,
                break;
        }
 
+       dump_dir = lp_parm_const_string(0, "dcesrv", "fuzz directory", NULL);
+
+       dcerpc_save_ndr_fuzz_seed(p,
+                                 p->in_data.data,
+                                 dump_dir,
+                                 interface_name,
+                                 NDR_IN,
+                                 pkt->u.request.opnum,
+                                 false);
+
        if (!srv_pipe_check_verification_trailer(p, pkt, pipe_fns)) {
                DEBUG(1, ("srv_pipe_check_verification_trailer: failed\n"));
                set_incoming_fault(p);
@@ -1487,6 +1498,14 @@ static bool api_pipe_request(struct pipes_struct *p,
                         &pipe_fns->syntax);
        unbecome_authenticated_pipe_user();
 
+       dcerpc_save_ndr_fuzz_seed(p,
+                                 p->out_data.rdata,
+                                 dump_dir,
+                                 interface_name,
+                                 NDR_OUT,
+                                 pkt->u.request.opnum,
+                                 false);
+
        TALLOC_FREE(frame);
        return ret;
 }