From 0a986869cc817b3bd798c76278fe48d65b1015e1 Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Tue, 4 Aug 2026 11:24:58 +0100 Subject: [PATCH] dechunker: Add handler for Dynamic channels --- common/dechunker.c | 182 ++++++++++++++++++++++++++++++++++++++++++++- common/dechunker.h | 105 ++++++++++++++++++++++++-- 2 files changed, 277 insertions(+), 10 deletions(-) diff --git a/common/dechunker.c b/common/dechunker.c index 7ead6d0c..c20e1801 100644 --- a/common/dechunker.c +++ b/common/dechunker.c @@ -57,11 +57,17 @@ struct vc_dechunker struct stream *reassembly_s; }; +struct dyn_dechunker +{ + char name[64]; + struct stream *reassembly_s; +}; + enum { // This is a rather arbitrary figure, but one we are unlikely to // go below. It's a sanity check for vc_dechunker_init() - E_MAX_CHUNK_SIZE_LOWER_LIMIT = 50 + E_MAX_VC_CHUNK_SIZE_LOWER_LIMIT = 50 }; /*****************************************************************************/ struct vc_dechunker * @@ -72,7 +78,7 @@ vc_dechunker_init(const char *chan_name, int max_chunk_size) { LOG(LOG_LEVEL_ERROR, "vc_dechunker_init() called with no channel name"); } - else if (max_chunk_size < E_MAX_CHUNK_SIZE_LOWER_LIMIT) + else if (max_chunk_size < E_MAX_VC_CHUNK_SIZE_LOWER_LIMIT) { LOG(LOG_LEVEL_ERROR, "Dechunker: Max chunk size for %s is too small", chan_name); @@ -237,6 +243,9 @@ handle_reading_state(struct vc_dechunker *self, // Tell the caller the stream is available. self->state = E_DATA; rv = E_VC_READY; + LOG_DEVEL(LOG_LEVEL_INFO, + "Dechunker: Reassembled PDU of size %d on %s", + self->reassembly_s->size, self->name); } else { @@ -369,3 +378,172 @@ vc_dechunker_get_stream(struct vc_dechunker *self) return s; } +/*****************************************************************************/ +struct dyn_dechunker * +dyn_dechunker_init(const char *chan_name) +{ + struct dyn_dechunker *self = NULL; + if (chan_name == NULL) + { + LOG(LOG_LEVEL_ERROR, + "dyn_dechunker_init() called with no channel name"); + } + else if ((self = g_new(struct dyn_dechunker, 1)) == NULL) + { + LOG(LOG_LEVEL_ERROR, "Dechunker: no memory for %s", chan_name); + } + else + { + strlcpy(self->name, chan_name, sizeof(self->name)); + self->reassembly_s = NULL; + } + + return self; +} + +/*****************************************************************************/ +void +dyn_dechunker_free(struct dyn_dechunker *self) +{ + if (self != NULL) + { + free_stream(self->reassembly_s); + free(self); + } +} + +/*****************************************************************************/ +enum dyn_dechunker_status +dyn_dechunker_process_first_chunk(struct dyn_dechunker *self, + struct stream *s, int total_size) +{ + enum dyn_dechunker_status status = E_DYN_ERROR; + + int frag_size = s ? s_rem(s) : 0; + if (self == NULL || s == NULL) + { + ; // Nothing to be done + } + else if (total_size <= 1590 || frag_size > total_size) + { + // See [MS-RDPEDYC] 2.2.3 + LOG(LOG_LEVEL_ERROR, + "Badly sized DYNVC_DATA_FIRST PDU received on dynamic channel %s", + self->name); + } + else if (self->reassembly_s != NULL) + { + LOG(LOG_LEVEL_ERROR, + "unexpected DYNVC_DATA_FIRST received on dynamic channel %s", + self->name); + } + else if (frag_size == total_size) + { + // This chunk ccontains all the data + status = E_DYN_INLINE_CHUNK; + } + else + { + make_stream(self->reassembly_s); + if (self->reassembly_s) + { + init_stream(self->reassembly_s, total_size); + } + if (self->reassembly_s == NULL || self->reassembly_s->data == NULL) + { + LOG(LOG_LEVEL_ERROR, + "Out of memory for dynamic PDU reassembly on %s", + self->name); + } + else + { + out_uint8p(self->reassembly_s, s->p, frag_size); + in_uint8s(s, frag_size); + status = E_DYN_IN_PROGRESS; + } + } + + return status; +} + +/*****************************************************************************/ +enum dyn_dechunker_status +dyn_dechunker_process_data_chunk(struct dyn_dechunker *self, + struct stream *s) +{ + enum dyn_dechunker_status rv; + + if (self == NULL || s == NULL) + { + rv = E_DYN_ERROR; + } + else if (self->reassembly_s == NULL) + { + rv = E_DYN_INLINE_CHUNK; + } + else + { + int frag_size = s_rem(s); + // We're currently reconstructing a data PDU from fragments + if (!s_check_rem_out(self-> reassembly_s, frag_size)) + { + LOG(LOG_LEVEL_ERROR, + "Oversized DYNVC_DATA when reconstructing PDU on %s", + self->name); + rv = E_DYN_ERROR; + } + else + { + out_uint8p(self->reassembly_s, s->p, frag_size); + in_uint8s(s, frag_size); + + if (s_rem_out(self->reassembly_s) == 0) + { + // Finished defragging + s_mark_end(self->reassembly_s); + self->reassembly_s->p = self->reassembly_s->data; + rv = E_DYN_READY; + LOG_DEVEL(LOG_LEVEL_INFO, + "Dechunker: Reassembled PDU of size %d on %s", + self->reassembly_s->size, self->name); + } + else + { + rv = E_DYN_IN_PROGRESS; + } + } + } + + return rv; +} + +/*****************************************************************************/ +struct stream * +dyn_dechunker_get_stream(struct dyn_dechunker *self) +{ + struct stream *s; + const char *name = (self != NULL) ? self->name : ""; + if (self == NULL || self->reassembly_s == NULL || + self->reassembly_s->end == self->reassembly_s->data) + { + LOG (LOG_LEVEL_ERROR, + "Dechunker: get stream called for %s with no data available", + name); + s = NULL; + } + else + { + // Pass ownership of the stream to the caller + s = self->reassembly_s; + self->reassembly_s = NULL; // So we don't free it ourselves! + } + + return s; +} + +/*****************************************************************************/ +int +dyn_dechunker_pending(struct dyn_dechunker *self) +{ + return (self != NULL && self->reassembly_s != NULL); +} diff --git a/common/dechunker.h b/common/dechunker.h index f3dcdbda..5bc6c8ce 100644 --- a/common/dechunker.h +++ b/common/dechunker.h @@ -34,11 +34,12 @@ struct stream; -/* Private type */ -struct vc_dechunker; +/* Private types */ +struct vc_dechunker; // static virtual channel dechunker +struct dyn_dechunker; // dynamic channel dechunker /** - * Returned from dechunker_process_vc_chunk() + * Returned from vc_dechunker_process_chunk() */ enum vc_dechunker_status { @@ -49,7 +50,19 @@ enum vc_dechunker_status }; /** - * Initialise a virtual channel dechunker + * Returned from dyn_dechunker_process_chunk() and + * Returned from dyn_dechunker_process_first_chunk() + */ +enum dyn_dechunker_status +{ + E_DYN_INLINE_CHUNK = 0, ///< This chunk is complete in itself + E_DYN_IN_PROGRESS, ///< The dechunker is processing chunks + E_DYN_READY, ///< A dechunked stream is now complete + E_DYN_ERROR ///< An error occurred (logged) +}; + +/** + * Initialise a static virtual channel dechunker * * @param chan_name - Name of channel * @param max_chunk_size - Max size of chunks allowed on channel @@ -59,14 +72,14 @@ struct vc_dechunker * vc_dechunker_init(const char *chan_name, int max_chunk_size); /** - * Free a virtual channel dechunker + * Free a static virtual channel dechunker * @param self vc dechunker to free */ void vc_dechunker_free(struct vc_dechunker *self); /** - * Process a virtual channel chunk + * Process a static virtual channel chunk * * @param self dechunker * @param s Stream for chunk, positioned at start of chunk @@ -84,9 +97,9 @@ enum vc_dechunker_status vc_dechunker_process_chunk(struct vc_dechunker *self, struct stream *s, int flags, int total_size); /** - * Get the stream from a ready dechunker + * Get the stream from a ready static virtual dechunker * - * @param self virtual channel dechunker + * @param self static virtual channel dechunker * @return input stream containing completed chunk * * Ownership of the stream passes to the caller @@ -97,4 +110,80 @@ struct stream * vc_dechunker_get_stream(struct vc_dechunker *self); +/** + * Initialise a dynamic virtual channel dechunker + * + * @param chan_name - Name of channel + * @return dyn_dechunker + */ +struct dyn_dechunker * +dyn_dechunker_init(const char *chan_name); + +/** + * Free a dynamic channel dechunker + * @param self dynamic dechunker to free + */ +void +dyn_dechunker_free(struct dyn_dechunker *self); + +/** + * Process a dynamic channel DYNVC_DATA_FIRST PDU ([MS-RDPEDYC] 2.2.3.1) + * + * @param self dechunker + * @param s Stream for chunk, positioned at start of data + * @param total_size length from DYNVC_DATA_FIRST header + * @return status of dechunker + * + * For PDUs of size > 1590 bytes, but < 1600, it is possible for the + * status E_DYN_INLINE_CHUNK to be returned, as the first chunk + * is complete in itself. This follows from [MS-RDPEDYC] 1.3.3.2.1 and + * 2.2.3.1 + * + * E_DYN_READY will not be returned by this call. + * + * On error, it is not possible to recover the stream, because of the + * impossibility of distinguishing self-contained DATA PDUs, and + * DATA PDUs which are part of a larger PDU. + */ +enum dyn_dechunker_status +dyn_dechunker_process_first_chunk(struct dyn_dechunker *self, + struct stream *s, int total_size); + +/** + * Process a dynamic channel DYNVC_DATA PDU ([MS-RDPEDYC] 2.2.3.2) + * + * @param self dechunker + * @param s Stream for chunk, positioned at start of data + * @return status of dechunker + * + * On error, it is not possible to recover the stream, because of the + * impossibility of distinguishing self-contained DATA PDUs, and + * DATA PDUs which are part of a larger PDU. + */ +enum dyn_dechunker_status +dyn_dechunker_process_data_chunk(struct dyn_dechunker *self, + struct stream *s); + +/** + * Get the stream from a ready dynamic dechunker + * + * @param self dynamic channel dechunker + * @return input stream containing completed chunk + * + * Ownership of the stream passes to the caller + * + * Resets the dechunker state so that further chunks can be processed. + */ +struct stream * +dyn_dechunker_get_stream(struct dyn_dechunker *self); + +/** + * Queries a dynamic dechunker for pending data + * + * @param self dynamic channel dechunker + * @return != 0 if data is stored in the dechunker + */ +int +dyn_dechunker_pending(struct dyn_dechunker *self); + #endif // DECHUNKER_H