-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Websockets 2695 v7.1 #10091
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Websockets 2695 v7.1 #10091
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| WebSocket Keywords | ||
| ================== | ||
|
|
||
| websocket.payload | ||
| ----------------- | ||
|
|
||
| A sticky buffer on the unmasked payload, | ||
| limited by suricata.yaml config value ``websocket.max-payload-size``. | ||
|
|
||
| Examples:: | ||
|
|
||
| websocket.payload; pcre:"/^123[0-9]*/"; | ||
| websocket.payload content:"swordfish"; | ||
|
|
||
| ``websocket.payload`` is a 'sticky buffer' and can be used as ``fast_pattern``. | ||
|
|
||
| websocket.fin | ||
| ------------- | ||
|
|
||
| A boolean to tell if the payload is complete. | ||
|
|
||
| Examples:: | ||
|
|
||
| websocket.fin:true; | ||
| websocket.fin:false; | ||
|
|
||
| websocket.mask | ||
| -------------- | ||
|
|
||
| Matches on the websocket mask if any. | ||
| It uses a 32-bit unsigned integer as value (big-endian). | ||
|
|
||
| Examples:: | ||
|
|
||
| websocket.mask:123456; | ||
| websocket.mask:>0; | ||
|
|
||
| websocket.opcode | ||
| ---------------- | ||
|
|
||
| Matches on the websocket opcode. | ||
| It uses a 8-bit unsigned integer as value. | ||
| Only 16 values are relevant. | ||
| It can also be specified by text from the enumeration | ||
|
|
||
| Examples:: | ||
|
|
||
| websocket.opcode:1; | ||
| websocket.opcode:>8; | ||
| websocket.opcode:ping; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| # WebSocket app-layer event rules. | ||
| # | ||
| # These SIDs fall in the 2235000+ range. See: | ||
| # http://doc.emergingthreats.net/bin/view/Main/SidAllocation and | ||
| # https://redmine.openinfosecfoundation.org/projects/suricata/wiki/AppLayer | ||
|
|
||
| alert websocket any any -> any any (msg:"SURICATA Websocket skipped end of payload"; app-layer-event:websocket.skip_end_of_payload; classtype:protocol-command-decode; sid:2235000; rev:1;) | ||
| alert websocket any any -> any any (msg:"SURICATA Websocket reassembly limit reached"; app-layer-event:websocket.reassembly_limit_reached; classtype:protocol-command-decode; sid:2235001; rev:1;) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| /* Copyright (C) 2023 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. | ||
| */ | ||
|
|
||
| extern crate proc_macro; | ||
| use super::applayerevent::transform_name; | ||
| use proc_macro::TokenStream; | ||
| use quote::quote; | ||
| use syn::{self, parse_macro_input, DeriveInput}; | ||
| use std::str::FromStr; | ||
|
|
||
| pub fn derive_enum_string<T: std::str::FromStr + quote::ToTokens>(input: TokenStream, ustr: &str) -> TokenStream where <T as FromStr>::Err: std::fmt::Display { | ||
| let input = parse_macro_input!(input as DeriveInput); | ||
| let name = input.ident; | ||
| let mut values = Vec::new(); | ||
| let mut names = Vec::new(); | ||
| let mut fields = Vec::new(); | ||
|
|
||
| if let syn::Data::Enum(ref data) = input.data { | ||
| let mut default_seen = false; | ||
| for (_, v) in (&data.variants).into_iter().enumerate() { | ||
| if let Some((_, val)) = &v.discriminant { | ||
| let fname = transform_name(&v.ident.to_string()); | ||
| names.push(fname); | ||
| fields.push(v.ident.clone()); | ||
| if let syn::Expr::Lit(l) = val { | ||
| if let syn::Lit::Int(li) = &l.lit { | ||
| if let Ok(value) = li.base10_parse::<T>() { | ||
| values.push(value); | ||
| } else { | ||
| panic!("EnumString requires explicit {}", ustr); | ||
| } | ||
| } else { | ||
| panic!("EnumString requires explicit literal integer"); | ||
| } | ||
| } else { | ||
| panic!("EnumString requires explicit literal"); | ||
| } | ||
| } else { | ||
| if let syn::Fields::Unnamed(f) = &v.fields { | ||
| if default_seen || f.unnamed.len() != 1 { | ||
| panic!("EnumString requires explicit values or one Unknown({})", ustr); | ||
| } | ||
| if v.ident.to_string() != "Unknown" { | ||
| panic!("EnumString default case must be Unknown({})", ustr); | ||
| } | ||
| if let syn::Type::Path(p) = &f.unnamed[0].ty { | ||
| if p.path.segments.len() != 1 { | ||
| panic!("EnumString requires explicit values or one Unknown({})", ustr); | ||
| } | ||
| if p.path.segments[0].ident.to_string() != ustr { | ||
| panic!("EnumString default case must be Unknown({})", ustr); | ||
| } | ||
| } else { | ||
| panic!("EnumString requires explicit values or one Unknown({})", ustr); | ||
| } | ||
| default_seen = true; | ||
| } else { | ||
| panic!("EnumString requires explicit values or one Unknown({})", ustr); | ||
| } | ||
| } | ||
| } | ||
| } else { | ||
| panic!("EnumString can only be derived for enums"); | ||
| } | ||
|
|
||
| let utype_str = syn::Ident::new(&ustr, proc_macro2::Span::call_site()); | ||
|
|
||
| let expanded = quote! { | ||
| impl #name { | ||
| pub(crate) fn from_u(v: #utype_str) -> Self { | ||
| match v { | ||
| #( #values => #name::#fields ,)* | ||
| _ => #name::Unknown(v), | ||
| } | ||
| } | ||
| pub(crate) fn into_u(&self) -> #utype_str { | ||
| match *self { | ||
| #( #name::#fields => #values ,)* | ||
| #name::Unknown(v) => v, | ||
| } | ||
| } | ||
| pub(crate) fn to_string(&self) -> String { | ||
| match *self { | ||
| #( #name::#fields => #names.to_string() ,)* | ||
| #name::Unknown(v) => format!("unknown-{}", v), | ||
| } | ||
| } | ||
| pub(crate) fn from_str(s: &str) -> Option<#name> { | ||
| match s { | ||
| #( #names => Some(#name::#fields) ,)* | ||
| _ => None | ||
| } | ||
| } | ||
| pub(crate) fn to_detect_ctx(s: &str) -> Option<DetectUintData<#utype_str>> { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this derive needs a new name as it does more than just to/from strings. It is helpful when a derive generates the implementation of a trait, like
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. More on using a trait. If we create a trait, We could then do something like: allowing anything implementing StringEnumU8 to be converted to a DetectUintData through standard interfaces.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok to make a trait. Not getting your second comment
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Just implement the From trait for DetectUintData alongside DetectUintData.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I get what you mean. But I do not really do So I think the prototype |
||
| if let Ok((_, ctx)) = detect_parse_uint::<#utype_str>(s) { | ||
| return Some(ctx); | ||
| } | ||
| if let Some(arg1) = #name::from_str(s) { | ||
| let arg1 = #name::into_u(&arg1); | ||
| let ctx = DetectUintData::<#utype_str> { | ||
| arg1, | ||
| arg2: 0, | ||
| mode: DetectUintMode::DetectUintModeEqual, | ||
| }; | ||
| return Some(ctx); | ||
| } | ||
| return None; | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| proc_macro::TokenStream::from(expanded) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| /* Copyright (C) 2023 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. | ||
| */ | ||
|
|
||
| use super::websocket::WebSocketTransaction; | ||
| use crate::detect::uint::DetectUintData; | ||
| use crate::websocket::parser::WebSocketOpcode; | ||
| use std::ffi::CStr; | ||
|
|
||
| #[no_mangle] | ||
| pub unsafe extern "C" fn SCWebSocketGetOpcode(tx: &mut WebSocketTransaction) -> u8 { | ||
| return WebSocketOpcode::into_u(&tx.pdu.opcode); | ||
| } | ||
|
|
||
| #[no_mangle] | ||
| pub unsafe extern "C" fn SCWebSocketGetFin(tx: &mut WebSocketTransaction) -> bool { | ||
| return tx.pdu.fin; | ||
| } | ||
|
|
||
| #[no_mangle] | ||
| pub unsafe extern "C" fn SCWebSocketGetPayload( | ||
| tx: &WebSocketTransaction, buffer: *mut *const u8, buffer_len: *mut u32, | ||
| ) -> bool { | ||
| *buffer = tx.pdu.payload.as_ptr(); | ||
| *buffer_len = tx.pdu.payload.len() as u32; | ||
| return true; | ||
| } | ||
|
|
||
| #[no_mangle] | ||
| pub unsafe extern "C" fn SCWebSocketGetMask( | ||
| tx: &mut WebSocketTransaction, value: *mut u32, | ||
| ) -> bool { | ||
| if let Some(xorkey) = tx.pdu.mask { | ||
| *value = xorkey; | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| #[no_mangle] | ||
| pub unsafe extern "C" fn SCWebSocketParseOpcode( | ||
| ustr: *const std::os::raw::c_char, | ||
| ) -> *mut DetectUintData<u8> { | ||
| let ft_name: &CStr = CStr::from_ptr(ustr); //unsafe | ||
| if let Ok(s) = ft_name.to_str() { | ||
| if let Some(ctx) = WebSocketOpcode::to_detect_ctx(s) { | ||
| let boxed = Box::new(ctx); | ||
| return Box::into_raw(boxed) as *mut _; | ||
| } | ||
| } | ||
| return std::ptr::null_mut(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just
fromandinto?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because they are standard keywords ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe into_u8 and from_u8 to be a little more verbose given the name of the proc macro.
Also, any other enums we can implement this on to show its broader use case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For example http2.errorcode, http2.frametype (checking all http2-specific keywords)