diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:39:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:39:49 +0000 |
commit | a0aa2307322cd47bbf416810ac0292925e03be87 (patch) | |
tree | 37076262a026c4b48c8a0e84f44ff9187556ca35 /src/app-layer-expectation.c | |
parent | Initial commit. (diff) | |
download | suricata-a0aa2307322cd47bbf416810ac0292925e03be87.tar.xz suricata-a0aa2307322cd47bbf416810ac0292925e03be87.zip |
Adding upstream version 1:7.0.3.upstream/1%7.0.3
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/app-layer-expectation.c')
-rw-r--r-- | src/app-layer-expectation.c | 394 |
1 files changed, 394 insertions, 0 deletions
diff --git a/src/app-layer-expectation.c b/src/app-layer-expectation.c new file mode 100644 index 0000000..7a456f8 --- /dev/null +++ b/src/app-layer-expectation.c @@ -0,0 +1,394 @@ +/* Copyright (C) 2017-2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \defgroup applayerexpectation Application Layer Expectation + * + * Handling of dynamic parallel connection for application layer similar + * to FTP. + * + * @{ + * + * Some protocols like FTP create dynamic parallel flow (called expectation). In + * order to assign a application layer protocol to these expectation, Suricata + * needs to parse message of the initial protocol and create and maintain a list + * of expected flow. + * + * Application layers must use the here described API to implement this mechanism. + * + * When parsing a application layer message describing a parallel flow, the + * application layer can call AppLayerExpectationCreate() to declare an + * expectation. By doing that the next flow coming with corresponding IP parameters + * will be assigned the specified application layer. The resulting Flow will + * also have a Flow storage set that can be retrieved at index + * AppLayerExpectationGetDataId(): + * + * ``` + * data = (char *)FlowGetStorageById(f, AppLayerExpectationGetFlowId()); + * ``` + * This storage can be used to store information that are only available in the + * parent connection and could be useful in the parent connection. For instance + * this is used by the FTP protocol to propagate information such as file name + * and ftp operation to the FTP data connection. + */ + +/** + * \file + * + * \author Eric Leblond <eric@regit.org> + */ + +#include "queue.h" +#include "suricata-common.h" + +#include "ippair-storage.h" +#include "flow-storage.h" + +#include "app-layer-expectation.h" + +#include "util-print.h" + +static IPPairStorageId g_ippair_expectation_id = { .id = -1 }; +static FlowStorageId g_flow_expectation_id = { .id = -1 }; + +SC_ATOMIC_DECLARE(uint32_t, expectation_count); + +#define EXPECTATION_TIMEOUT 30 +#define EXPECTATION_MAX_LEVEL 10 + +typedef struct Expectation_ { + SCTime_t ts; + Port sp; + Port dp; + AppProto alproto; + int direction; + /* use pointer to Flow as identifier of the Flow the expectation is linked to */ + void *orig_f; + void *data; + CIRCLEQ_ENTRY(Expectation_) entries; +} Expectation; + +typedef struct ExpectationData_ { + /** Start of Expectation Data structure must be a pointer + * to free function. Set to NULL to use SCFree() */ + void (*DFree)(void *); +} ExpectationData; + +typedef struct ExpectationList_ { + CIRCLEQ_HEAD(EList, Expectation_) list; + uint8_t length; +} ExpectationList; + +static void ExpectationDataFree(void *e) +{ + SCLogDebug("Free expectation data"); + ExpectationData *ed = (ExpectationData *) e; + if (ed->DFree) { + ed->DFree(e); + } else { + SCFree(e); + } +} + +/** + * Free expectation + */ +static void AppLayerFreeExpectation(Expectation *exp) +{ + if (exp->data) { + ExpectationData *expdata = (ExpectationData *)exp->data; + if (expdata->DFree) { + expdata->DFree(exp->data); + } else { + SCFree(exp->data); + } + } + SCFree(exp); +} + +static void ExpectationListFree(void *el) +{ + ExpectationList *exp_list = (ExpectationList *)el; + if (exp_list == NULL) + return; + + if (exp_list->length > 0) { + Expectation *exp = NULL, *pexp = NULL; + CIRCLEQ_FOREACH_SAFE(exp, &exp_list->list, entries, pexp) { + CIRCLEQ_REMOVE(&exp_list->list, exp, entries); + exp_list->length--; + AppLayerFreeExpectation(exp); + } + } + SCFree(exp_list); +} + +uint64_t ExpectationGetCounter(void) +{ + uint64_t x = SC_ATOMIC_GET(expectation_count); + return x; +} + +void AppLayerExpectationSetup(void) +{ + g_ippair_expectation_id = + IPPairStorageRegister("expectation", sizeof(void *), NULL, ExpectationListFree); + g_flow_expectation_id = + FlowStorageRegister("expectation", sizeof(void *), NULL, ExpectationDataFree); + SC_ATOMIC_INIT(expectation_count); +} + +static inline int GetFlowAddresses(Flow *f, Address *ip_src, Address *ip_dst) +{ + memset(ip_src, 0, sizeof(*ip_src)); + memset(ip_dst, 0, sizeof(*ip_dst)); + if (FLOW_IS_IPV4(f)) { + FLOW_COPY_IPV4_ADDR_TO_PACKET(&f->src, ip_src); + FLOW_COPY_IPV4_ADDR_TO_PACKET(&f->dst, ip_dst); + } else if (FLOW_IS_IPV6(f)) { + FLOW_COPY_IPV6_ADDR_TO_PACKET(&f->src, ip_src); + FLOW_COPY_IPV6_ADDR_TO_PACKET(&f->dst, ip_dst); + } else { + return -1; + } + return 0; +} + +static ExpectationList *AppLayerExpectationLookup(Flow *f, IPPair **ipp) +{ + Address ip_src, ip_dst; + if (GetFlowAddresses(f, &ip_src, &ip_dst) == -1) + return NULL; + *ipp = IPPairLookupIPPairFromHash(&ip_src, &ip_dst); + if (*ipp == NULL) { + return NULL; + } + + return IPPairGetStorageById(*ipp, g_ippair_expectation_id); +} + + +static ExpectationList *AppLayerExpectationRemove(IPPair *ipp, + ExpectationList *exp_list, + Expectation *exp) +{ + CIRCLEQ_REMOVE(&exp_list->list, exp, entries); + AppLayerFreeExpectation(exp); + SC_ATOMIC_SUB(expectation_count, 1); + exp_list->length--; + if (exp_list->length == 0) { + IPPairSetStorageById(ipp, g_ippair_expectation_id, NULL); + ExpectationListFree(exp_list); + exp_list = NULL; + } + return exp_list; +} + +/** + * Create an entry in expectation list + * + * Create a expectation from an existing Flow. Currently, only Flow between + * the two original IP addresses are supported. In case of success, the + * ownership of the data pointer is taken. In case of error, the pointer + * to data has to be freed by the caller. + * + * \param f a pointer to the original Flow + * \param direction the direction of the data in the expectation flow + * \param src source port of the expected flow, use 0 for any + * \param dst destination port of the expected flow, use 0 for any + * \param alproto the protocol that need to be set on the expected flow + * \param data pointer to data that will be attached to the expected flow + * + * \return -1 if error + * \return 0 if success + */ +int AppLayerExpectationCreate(Flow *f, int direction, Port src, Port dst, + AppProto alproto, void *data) +{ + ExpectationList *exp_list = NULL; + IPPair *ipp; + Address ip_src, ip_dst; + + Expectation *exp = SCCalloc(1, sizeof(*exp)); + if (exp == NULL) + return -1; + + exp->sp = src; + exp->dp = dst; + exp->alproto = alproto; + exp->ts = f->lastts; + exp->orig_f = (void *)f; + exp->data = data; + exp->direction = direction; + + if (GetFlowAddresses(f, &ip_src, &ip_dst) == -1) + goto error; + ipp = IPPairGetIPPairFromHash(&ip_src, &ip_dst); + if (ipp == NULL) + goto error; + + exp_list = IPPairGetStorageById(ipp, g_ippair_expectation_id); + if (exp_list) { + CIRCLEQ_INSERT_HEAD(&exp_list->list, exp, entries); + /* In case there is already EXPECTATION_MAX_LEVEL expectations waiting to be fulfilled, + * we remove the older expectation to limit the total number of expectations */ + if (exp_list->length >= EXPECTATION_MAX_LEVEL) { + Expectation *last_exp = CIRCLEQ_LAST(&exp_list->list); + CIRCLEQ_REMOVE(&exp_list->list, last_exp, entries); + AppLayerFreeExpectation(last_exp); + /* We keep the same amount of expectation so we fully release + * the IP pair */ + f->flags |= FLOW_HAS_EXPECTATION; + IPPairRelease(ipp); + return 0; + } + } else { + exp_list = SCCalloc(1, sizeof(*exp_list)); + if (exp_list == NULL) + goto error; + exp_list->length = 0; + CIRCLEQ_INIT(&exp_list->list); + CIRCLEQ_INSERT_HEAD(&exp_list->list, exp, entries); + IPPairSetStorageById(ipp, g_ippair_expectation_id, exp_list); + } + + exp_list->length += 1; + SC_ATOMIC_ADD(expectation_count, 1); + f->flags |= FLOW_HAS_EXPECTATION; + /* As we are creating the expectation, we release lock on IPPair without + * setting the ref count to 0. This way the IPPair will be kept till + * cleanup */ + IPPairUnlock(ipp); + return 0; + +error: + SCFree(exp); + return -1; +} + +/** + * Return Flow storage identifier corresponding to expectation data + * + * \return expectation data identifier + */ +FlowStorageId AppLayerExpectationGetFlowId(void) +{ + return g_flow_expectation_id; +} + +/** + * Function doing a lookup in expectation list and updating Flow if needed. + * + * This function lookup for a existing expectation that could match the Flow. + * If found and if the expectation contains data it store the data in the + * expectation storage of the Flow. + * + * \return an AppProto value if found + * \return ALPROTO_UNKNOWN if not found + */ +AppProto AppLayerExpectationHandle(Flow *f, uint8_t flags) +{ + AppProto alproto = ALPROTO_UNKNOWN; + IPPair *ipp = NULL; + Expectation *lexp = NULL; + Expectation *exp = NULL; + + int x = SC_ATOMIC_GET(expectation_count); + if (x == 0) { + return ALPROTO_UNKNOWN; + } + + /* Call will take reference of the ip pair in 'ipp' */ + ExpectationList *exp_list = AppLayerExpectationLookup(f, &ipp); + if (exp_list == NULL) + goto out; + + CIRCLEQ_FOREACH_SAFE(exp, &exp_list->list, entries, lexp) { + if ((exp->direction & flags) && ((exp->sp == 0) || (exp->sp == f->sp)) && + ((exp->dp == 0) || (exp->dp == f->dp))) { + alproto = exp->alproto; + if (f->alproto_ts == ALPROTO_UNKNOWN) { + f->alproto_ts = alproto; + } + if (f->alproto_tc == ALPROTO_UNKNOWN) { + f->alproto_tc = alproto; + } + void *fdata = FlowGetStorageById(f, g_flow_expectation_id); + if (fdata) { + /* We already have an expectation so let's clean this one */ + ExpectationDataFree(exp->data); + } else { + /* Transfer ownership of Expectation data to the Flow */ + if (FlowSetStorageById(f, g_flow_expectation_id, exp->data) != 0) { + SCLogDebug("Unable to set flow storage"); + } + } + exp->data = NULL; + exp_list = AppLayerExpectationRemove(ipp, exp_list, exp); + if (exp_list == NULL) + goto out; + continue; + } + /* Cleaning remove old entries */ + if (SCTIME_SECS(f->lastts) > SCTIME_SECS(exp->ts) + EXPECTATION_TIMEOUT) { + exp_list = AppLayerExpectationRemove(ipp, exp_list, exp); + if (exp_list == NULL) + goto out; + continue; + } + } + +out: + if (ipp) + IPPairRelease(ipp); + return alproto; +} + +void AppLayerExpectationClean(Flow *f) +{ + IPPair *ipp = NULL; + Expectation *exp = NULL; + Expectation *pexp = NULL; + + int x = SC_ATOMIC_GET(expectation_count); + if (x == 0) { + return; + } + + /* Call will take reference of the ip pair in 'ipp' */ + ExpectationList *exp_list = AppLayerExpectationLookup(f, &ipp); + if (exp_list == NULL) + goto out; + + CIRCLEQ_FOREACH_SAFE(exp, &exp_list->list, entries, pexp) { + /* Cleaning remove old entries */ + if (exp->orig_f == (void *)f) { + exp_list = AppLayerExpectationRemove(ipp, exp_list, exp); + if (exp_list == NULL) + goto out; + } + } + +out: + if (ipp) + IPPairRelease(ipp); + return; +} + +/** + * @} + */ |