/* 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 */ #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; } /** * @} */