From: Douglas Bagnall Date: Wed, 6 Nov 2019 04:27:08 +0000 (+1300) Subject: dcerpc: developer option to save ndr_fuzz_X seeds X-Git-Tag: ldb-2.1.0~328 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e737988641905180d7d9a4e5cc88a85637deaab6;p=thirdparty%2Fsamba.git dcerpc: developer option to save ndr_fuzz_X seeds Signed-off-by: Douglas Bagnall Signed-off-by: Andrew Bartlett Pair-programmed-by: Andrew Bartlett Reviewed-by: Andrew Bartlett --- diff --git a/librpc/rpc/dcerpc_util.c b/librpc/rpc/dcerpc_util.c index 7e2d06e0cef..f7596cb1ac1 100644 --- a/librpc/rpc/dcerpc_util.c +++ b/librpc/rpc/dcerpc_util.c @@ -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 /* 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() */ diff --git a/librpc/rpc/dcesrv_core.c b/librpc/rpc/dcesrv_core.c index b7360abfcf7..daa95afcab7 100644 --- a/librpc/rpc/dcesrv_core.c +++ b/librpc/rpc/dcesrv_core.c @@ -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", diff --git a/librpc/rpc/dcesrv_core.h b/librpc/rpc/dcesrv_core.h index fc2651bd9d3..5e47976ced9 100644 --- a/librpc/rpc/dcesrv_core.h +++ b/librpc/rpc/dcesrv_core.h @@ -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_ */ diff --git a/librpc/rpc/dcesrv_reply.c b/librpc/rpc/dcesrv_reply.c index a217d5a5610..96bd98f53e1 100644 --- a/librpc/rpc/dcesrv_reply.c +++ b/librpc/rpc/dcesrv_reply.c @@ -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 diff --git a/librpc/rpc/rpc_common.h b/librpc/rpc/rpc_common.h index 3e98c205fcf..a606a29ed31 100644 --- a/librpc/rpc/rpc_common.h +++ b/librpc/rpc/rpc_common.h @@ -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__ */ diff --git a/librpc/wscript_build b/librpc/wscript_build index 88964ed2a9a..b9e62859f5d 100644 --- a/librpc/wscript_build +++ b/librpc/wscript_build @@ -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', diff --git a/selftest/target/Samba3.pm b/selftest/target/Samba3.pm index 41d439ea91a..cdbbbdcef3d 100755 --- a/selftest/target/Samba3.pm +++ b/selftest/target/Samba3.pm @@ -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 diff --git a/source3/rpc_server/srv_pipe.c b/source3/rpc_server/srv_pipe.c index fa5043e8c1f..3bc291fb0c9 100644 --- a/source3/rpc_server/srv_pipe.c +++ b/source3/rpc_server/srv_pipe.c @@ -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; }