mirror of https://github.com/OISF/suricata
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
178 lines
6.3 KiB
C
178 lines
6.3 KiB
C
/* Copyright (C) 2026 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.
|
|
*/
|
|
|
|
/**
|
|
* \file
|
|
*/
|
|
|
|
#include "suricata-common.h"
|
|
#include "decode.h"
|
|
#include "util-debug.h"
|
|
|
|
#define IGMP_TYPE_MEMBERSHIP_QUERY 0x11
|
|
#define IGMP_TYPE_MEMBERSHIP_REPORT_V1 0x12
|
|
#define IGMP_TYPE_MEMBERSHIP_REPORT_V2 0x16
|
|
#define IGMP_TYPE_LEAVE_GROUP_V2 0x17
|
|
#define IGMP_TYPE_MEMBERSHIP_REPORT_V3 0x22
|
|
|
|
/* Data size: IP segment size (64k), minus IP header + RA option (24),
|
|
* minus IGMPv3 header (12).
|
|
* Divided by 4 bytes per address. */
|
|
#define IGMP_V3_MAX_N_SOURCES (65535 - 24 - 12) / 4
|
|
|
|
typedef struct IGMPv3MemberQueryHdr_ {
|
|
uint8_t type;
|
|
uint8_t max_resp_time;
|
|
uint16_t checksum;
|
|
uint32_t group_address;
|
|
uint8_t s_qrv;
|
|
uint8_t qqic;
|
|
uint16_t n_sources;
|
|
/* Followed by source addresses */
|
|
} IGMPv3MemberQueryHdr;
|
|
|
|
typedef struct IGMPv3MemberReportGroupRecord_ {
|
|
uint8_t type;
|
|
uint8_t aux_data_len; /**< in units of 32-bit words */
|
|
uint16_t n_sources;
|
|
uint32_t mcast_addr;
|
|
// source address
|
|
// aux data
|
|
} IGMPv3MemberReportGroupRecord;
|
|
|
|
typedef struct IGMPv3MemberReportHdr_ {
|
|
uint8_t type;
|
|
uint8_t res;
|
|
uint16_t checksum;
|
|
uint16_t flags;
|
|
uint16_t n_group_recs;
|
|
/* Followed by group records */
|
|
|
|
} IGMPv3MemberReportHdr;
|
|
|
|
int DecodeIGMP(ThreadVars *tv, DecodeThreadVars *dtv, Packet *p, const uint8_t *pkt, uint32_t len)
|
|
{
|
|
StatsCounterIncr(&tv->stats, dtv->counter_igmp);
|
|
|
|
if (len < sizeof(IGMPHdr)) {
|
|
ENGINE_SET_INVALID_EVENT(p, IGMP_PKT_TOO_SMALL);
|
|
return TM_ECODE_FAILED;
|
|
}
|
|
|
|
const IGMPHdr *igmp = PacketSetIGMP(p, pkt);
|
|
p->proto = IPPROTO_IGMP;
|
|
uint8_t version;
|
|
|
|
/* For IGMPv3 Membership Query, we need to handle additional fields */
|
|
if (igmp->type == IGMP_TYPE_MEMBERSHIP_QUERY) {
|
|
if (len >= 12) {
|
|
const IGMPv3MemberQueryHdr *igmpv3 = (const IGMPv3MemberQueryHdr *)pkt;
|
|
const uint32_t n_sources = SCNtohs(igmpv3->n_sources);
|
|
if (n_sources > IGMP_V3_MAX_N_SOURCES) {
|
|
ENGINE_SET_INVALID_EVENT(p, IGMP_MALFORMED);
|
|
return TM_ECODE_FAILED;
|
|
}
|
|
|
|
const uint32_t header_len =
|
|
sizeof(IGMPv3MemberQueryHdr) + (n_sources * sizeof(uint32_t));
|
|
if (len < header_len) {
|
|
SCLogDebug("len %u < header_len %u", len, header_len);
|
|
ENGINE_SET_INVALID_EVENT(p, IGMP_V3_PKT_TOO_SMALL);
|
|
return TM_ECODE_FAILED;
|
|
}
|
|
if (header_len >= UINT16_MAX) {
|
|
ENGINE_SET_INVALID_EVENT(p, IGMP_MALFORMED);
|
|
return TM_ECODE_FAILED;
|
|
}
|
|
p->l4.vars.igmp.hlen = (uint16_t)header_len;
|
|
p->payload = (uint8_t *)pkt + header_len;
|
|
p->payload_len = (uint16_t)(len - header_len);
|
|
version = 3;
|
|
} else {
|
|
if (igmp->max_resp_time == 0) {
|
|
version = 1;
|
|
} else {
|
|
version = 2;
|
|
}
|
|
p->l4.vars.igmp.hlen = (uint16_t)sizeof(IGMPHdr);
|
|
p->payload = (uint8_t *)pkt + sizeof(IGMPHdr);
|
|
p->payload_len = (uint16_t)(len - sizeof(IGMPHdr));
|
|
}
|
|
|
|
} else if (igmp->type == IGMP_TYPE_MEMBERSHIP_REPORT_V3) {
|
|
const IGMPv3MemberReportHdr *igmpv3 = (const IGMPv3MemberReportHdr *)pkt;
|
|
const uint32_t n_group_recs = SCNtohs(igmpv3->n_group_recs);
|
|
uint32_t header_len = sizeof(IGMPv3MemberReportHdr);
|
|
|
|
/* parse group records */
|
|
for (uint32_t i = 0; i < n_group_recs; i++) {
|
|
if (len - header_len < sizeof(IGMPv3MemberReportGroupRecord)) {
|
|
SCLogDebug("len %u - header_len %u < IGMPv3MemberReportGroupRecord %u", len,
|
|
header_len, (uint32_t)sizeof(IGMPv3MemberReportGroupRecord));
|
|
ENGINE_SET_INVALID_EVENT(p, IGMP_V3_PKT_TOO_SMALL);
|
|
return TM_ECODE_FAILED;
|
|
}
|
|
|
|
const IGMPv3MemberReportGroupRecord *grec =
|
|
(const IGMPv3MemberReportGroupRecord *)(pkt + header_len);
|
|
header_len += sizeof(IGMPv3MemberReportGroupRecord);
|
|
|
|
if (len - header_len < (uint32_t)(grec->aux_data_len * sizeof(uint32_t))) {
|
|
SCLogDebug("len %u - header_len %u aux_data_len %u", len, header_len,
|
|
(uint32_t)grec->aux_data_len);
|
|
ENGINE_SET_INVALID_EVENT(p, IGMP_MALFORMED);
|
|
return TM_ECODE_FAILED;
|
|
}
|
|
header_len += (uint32_t)(grec->aux_data_len * sizeof(uint32_t));
|
|
|
|
uint32_t sources_len = (uint32_t)SCNtohs(grec->n_sources) * (uint32_t)sizeof(uint32_t);
|
|
if (len - header_len < sources_len) {
|
|
SCLogDebug("len %u - header_len %u sources_len %u", len, header_len, sources_len);
|
|
ENGINE_SET_INVALID_EVENT(p, IGMP_MALFORMED);
|
|
return TM_ECODE_FAILED;
|
|
}
|
|
header_len += sources_len;
|
|
}
|
|
if (header_len >= UINT16_MAX) {
|
|
ENGINE_SET_INVALID_EVENT(p, IGMP_MALFORMED);
|
|
return TM_ECODE_FAILED;
|
|
}
|
|
p->l4.vars.igmp.hlen = (uint16_t)header_len;
|
|
p->payload = (uint8_t *)pkt + header_len;
|
|
p->payload_len = (uint16_t)(len - header_len);
|
|
version = 3;
|
|
|
|
} else {
|
|
if (igmp->type == IGMP_TYPE_MEMBERSHIP_REPORT_V1) {
|
|
version = 1;
|
|
|
|
} else if (igmp->type == IGMP_TYPE_MEMBERSHIP_REPORT_V2 ||
|
|
igmp->type == IGMP_TYPE_LEAVE_GROUP_V2) {
|
|
version = 2;
|
|
} else {
|
|
version = 255;
|
|
}
|
|
|
|
p->l4.vars.igmp.hlen = (uint16_t)sizeof(IGMPHdr);
|
|
p->payload = (uint8_t *)pkt + sizeof(IGMPHdr);
|
|
p->payload_len = (uint16_t)(len - sizeof(IGMPHdr));
|
|
}
|
|
p->l4.vars.igmp.version = version;
|
|
|
|
return TM_ECODE_OK;
|
|
}
|