Skip to content

Commit 9fad97a

Browse files
authored
Security1 (#216)
Fixes for potential security issues #211 & #212 - excessive chunks & decoding recursion checks on variants / extension objects
1 parent f58359b commit 9fad97a

36 files changed

+447
-169
lines changed

lib/src/client/builder.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,18 @@ impl ClientBuilder {
273273
self.config.session_name = session_name.into();
274274
self
275275
}
276+
277+
/// Set the maximum message size
278+
pub fn max_message_size(mut self, max_message_size: usize) -> Self {
279+
self.config.decoding_options.max_message_size = max_message_size;
280+
self
281+
}
282+
283+
/// Set the max chunk count
284+
pub fn max_chunk_count(mut self, max_chunk_count: usize) -> Self {
285+
self.config.decoding_options.max_chunk_count = max_chunk_count;
286+
self
287+
}
276288
}
277289

278290
#[test]

lib/src/client/client.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,10 +427,12 @@ impl Client {
427427
let decoding_options = &self.config.decoding_options;
428428
DecodingOptions {
429429
max_chunk_count: decoding_options.max_chunk_count,
430+
max_message_size: decoding_options.max_message_size,
430431
max_string_length: decoding_options.max_string_length,
431432
max_byte_string_length: decoding_options.max_byte_string_length,
432433
max_array_length: decoding_options.max_array_length,
433434
client_offset: Duration::zero(),
435+
..Default::default()
434436
}
435437
}
436438

lib/src/client/comms/tcp_transport.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ impl ReadState {
132132
let chunks_len = self.chunks.len();
133133
if self.max_chunk_count > 0 && chunks_len > self.max_chunk_count {
134134
error!("too many chunks {}> {}", chunks_len, self.max_chunk_count);
135+
// TODO this code should return an error to be safe
135136
//remove first
136137
let first_req_id = *self.chunks.iter().next().unwrap().0;
137138
self.chunks.remove(&first_req_id);
@@ -312,7 +313,7 @@ impl TcpTransport {
312313
pub fn connect(&self, endpoint_url: &str) -> Result<(), StatusCode> {
313314
debug_assert!(!self.is_connected(), "Should not try to connect when already connected");
314315
let (host, port) =
315-
hostname_port_from_url(endpoint_url, constants::DEFAULT_OPC_UA_SERVER_PORT)?;
316+
hostname_port_from_url(endpoint_url, crate::core::constants::DEFAULT_OPC_UA_SERVER_PORT)?;
316317

317318
// Resolve the host name into a socket address
318319
let addr = {

lib/src/client/config.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ impl ClientEndpoint {
136136
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
137137
pub struct DecodingOptions {
138138
/// Maximum size of a message chunk in bytes. 0 means no limit
139+
pub max_message_size: usize,
140+
/// Maximum number of chunks in a message. 0 means no limit
139141
pub max_chunk_count: usize,
140142
/// Maximum length in bytes (not chars!) of a string. 0 actually means 0, i.e. no string permitted
141143
pub max_string_length: usize,
@@ -311,7 +313,6 @@ impl ClientConfig {
311313
pki_dir.push(Self::PKI_DIR);
312314

313315
let decoding_options = crate::types::DecodingOptions::default();
314-
315316
ClientConfig {
316317
application_name: application_name.into(),
317318
application_uri: application_uri.into(),
@@ -334,6 +335,7 @@ impl ClientConfig {
334335
max_string_length: decoding_options.max_string_length,
335336
max_byte_string_length: decoding_options.max_byte_string_length,
336337
max_chunk_count: decoding_options.max_chunk_count,
338+
max_message_size: decoding_options.max_message_size,
337339
},
338340
performance: Performance {
339341
ignore_clock_skew: false,

lib/src/client/session/session_state.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,6 @@ impl SessionState {
157157
const SEND_BUFFER_SIZE: usize = 65535;
158158
const RECEIVE_BUFFER_SIZE: usize = 65535;
159159
const MAX_BUFFER_SIZE: usize = 65535;
160-
const MAX_CHUNK_COUNT: usize = 0;
161160

162161
pub fn new(
163162
ignore_clock_skew: bool,
@@ -176,7 +175,7 @@ impl SessionState {
176175
send_buffer_size: Self::SEND_BUFFER_SIZE,
177176
receive_buffer_size: Self::RECEIVE_BUFFER_SIZE,
178177
max_message_size: Self::MAX_BUFFER_SIZE,
179-
max_chunk_count: Self::MAX_CHUNK_COUNT,
178+
max_chunk_count: constants::MAX_CHUNK_COUNT,
180179
request_handle: Handle::new(Self::FIRST_REQUEST_HANDLE),
181180
session_id: NodeId::null(),
182181
authentication_token: NodeId::null(),

lib/src/core/comms/message_chunk.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,8 @@ impl BinaryEncoder<MessageChunk> for MessageChunk {
158158
})?;
159159

160160
let message_size = chunk_header.message_size as usize;
161-
if decoding_options.max_chunk_count > 0 && message_size > decoding_options.max_chunk_count {
161+
if decoding_options.max_message_size > 0 && message_size > decoding_options.max_message_size
162+
{
162163
// Message_size should be sanity checked and rejected if too large.
163164
Err(StatusCode::BadTcpMessageTooLarge)
164165
} else {

lib/src/core/comms/message_writer.rs

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -66,25 +66,37 @@ impl MessageWriter {
6666
&message,
6767
)?;
6868

69-
// Sequence number monotonically increases per chunk
70-
self.last_sent_sequence_number += chunks.len() as u32;
69+
if self.max_chunk_count > 0 && chunks.len() > self.max_chunk_count {
70+
error!(
71+
"Cannot write message since {} chunks exceeds {} chunk limit",
72+
chunks.len(),
73+
self.max_chunk_count
74+
);
75+
Err(StatusCode::BadCommunicationError)
76+
} else {
77+
// Sequence number monotonically increases per chunk
78+
self.last_sent_sequence_number += chunks.len() as u32;
7179

72-
// Send chunks
80+
// Send chunks
7381

74-
// This max chunk size allows the message to be encoded to a chunk with header + encoding
75-
// which is just slightly larger in size (up to 1024 bytes).
76-
let max_chunk_count = self.buffer.get_ref().len() + 1024;
77-
let mut data = vec![0u8; max_chunk_count];
78-
for chunk in chunks {
79-
trace!("Sending chunk {:?}", chunk);
80-
let size = secure_channel.apply_security(&chunk, &mut data)?;
81-
self.buffer.write(&data[..size]).map_err(|error| {
82-
error!("Error while writing bytes to stream, connection broken, check error {:?}", error);
83-
StatusCode::BadCommunicationError
84-
})?;
82+
// This max chunk size allows the message to be encoded to a chunk with header + encoding
83+
// which is just slightly larger in size (up to 1024 bytes).
84+
let data_buffer_size = self.buffer.get_ref().len() + 1024;
85+
let mut data = vec![0u8; data_buffer_size];
86+
for chunk in chunks {
87+
trace!("Sending chunk {:?}", chunk);
88+
let size = secure_channel.apply_security(&chunk, &mut data)?;
89+
self.buffer.write(&data[..size]).map_err(|error| {
90+
error!(
91+
"Error while writing bytes to stream, connection broken, check error {:?}",
92+
error
93+
);
94+
StatusCode::BadCommunicationError
95+
})?;
96+
}
97+
trace!("Message written");
98+
Ok(request_id)
8599
}
86-
trace!("Message written");
87-
Ok(request_id)
88100
}
89101

90102
pub fn next_request_id(&mut self) -> u32 {

lib/src/core/comms/secure_channel.rs

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,14 @@ pub struct SecureChannel {
7070
decoding_options: DecodingOptions,
7171
}
7272

73-
impl From<(SecurityPolicy, MessageSecurityMode)> for SecureChannel {
74-
fn from(v: (SecurityPolicy, MessageSecurityMode)) -> Self {
73+
impl SecureChannel {
74+
/// For testing purposes only
75+
#[cfg(test)]
76+
pub fn new_no_certificate_store() -> SecureChannel {
7577
SecureChannel {
7678
role: Role::Unknown,
77-
security_policy: v.0,
78-
security_mode: v.1,
79+
security_policy: SecurityPolicy::None,
80+
security_mode: MessageSecurityMode::None,
7981
secure_channel_id: 0,
8082
token_id: 0,
8183
token_created_at: DateTime::now(),
@@ -90,14 +92,6 @@ impl From<(SecurityPolicy, MessageSecurityMode)> for SecureChannel {
9092
decoding_options: DecodingOptions::default(),
9193
}
9294
}
93-
}
94-
95-
impl SecureChannel {
96-
/// For testing purposes only
97-
#[cfg(test)]
98-
pub fn new_no_certificate_store() -> SecureChannel {
99-
(SecurityPolicy::None, MessageSecurityMode::None).into()
100-
}
10195

10296
pub fn new(
10397
certificate_store: Arc<RwLock<CertificateStore>>,
@@ -222,7 +216,7 @@ impl SecureChannel {
222216
}
223217

224218
pub fn decoding_options(&self) -> DecodingOptions {
225-
self.decoding_options
219+
self.decoding_options.clone()
226220
}
227221

228222
/// Test if the secure channel token needs to be renewed. The algorithm determines it needs

lib/src/core/comms/security_header.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,7 @@ impl BinaryEncoder<AsymmetricSecurityHeader> for AsymmetricSecurityHeader {
8888

8989
// validate sender_certificate_length < MaxCertificateSize
9090
if sender_certificate.value.is_some()
91-
&& sender_certificate.value.as_ref().unwrap().len()
92-
>= constants::MAX_CERTIFICATE_LENGTH as usize
91+
&& sender_certificate.value.as_ref().unwrap().len() >= constants::MAX_CERTIFICATE_LENGTH
9392
{
9493
error!("Sender certificate exceeds max certificate size");
9594
Err(StatusCode::BadDecodingError)

lib/src/core/comms/url.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
77
use url::Url;
88

9-
use crate::types::{constants::DEFAULT_OPC_UA_SERVER_PORT, status_code::StatusCode};
9+
use crate::types::status_code::StatusCode;
1010

1111
pub const OPC_TCP_SCHEME: &str = "opc.tcp";
1212

@@ -16,7 +16,7 @@ fn opc_url_from_str(s: &str) -> Result<Url, ()> {
1616
.map(|mut url| {
1717
if url.port().is_none() {
1818
// If no port is supplied, then treat it as the default port 4840
19-
let _ = url.set_port(Some(DEFAULT_OPC_UA_SERVER_PORT));
19+
let _ = url.set_port(Some(crate::core::constants::DEFAULT_OPC_UA_SERVER_PORT));
2020
}
2121
url
2222
})
@@ -57,7 +57,7 @@ pub fn server_url_from_endpoint_url(endpoint_url: &str) -> std::result::Result<S
5757
url.set_query(None);
5858
if let Some(port) = url.port() {
5959
// If the port is the default, strip it so the url string omits it.
60-
if port == DEFAULT_OPC_UA_SERVER_PORT {
60+
if port == crate::core::constants::DEFAULT_OPC_UA_SERVER_PORT {
6161
let _ = url.set_port(None);
6262
}
6363
}

lib/src/core/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,13 @@ pub mod debug {
9393
#[cfg(test)]
9494
pub mod tests;
9595

96+
pub mod constants {
97+
/// Default OPC UA port number. Used by a discovery server. Other servers would normally run
98+
/// on a different port. So OPC UA for Rust does not use this nr by default but it is used
99+
/// implicitly in opc.tcp:// urls and elsewhere.
100+
pub const DEFAULT_OPC_UA_SERVER_PORT: u16 = 4840;
101+
}
102+
96103
pub mod comms;
97104
pub mod config;
98105
pub mod handle;

lib/src/core/tests/chunk.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ fn sample_secure_channel_request_data_security_none() -> MessageChunk {
3636

3737
// Decode chunk from stream
3838
stream.set_position(0);
39-
let decoding_options = DecodingOptions::default();
39+
let decoding_options = DecodingOptions::test();
4040
let chunk = MessageChunk::decode(&mut stream, &decoding_options).unwrap();
4141

4242
println!(
@@ -102,6 +102,7 @@ fn chunk_multi_encode_decode() {
102102
max_string_length: 65535,
103103
max_byte_string_length: 65535,
104104
max_array_length: 20000, // Need to bump this up because large response uses a large array
105+
..Default::default()
105106
});
106107

107108
let response = make_large_read_response();
@@ -152,7 +153,7 @@ fn chunk_multi_chunk_intermediate_final() {
152153
.unwrap();
153154
assert!(chunks.len() > 1);
154155

155-
let decoding_options = DecodingOptions::default();
156+
let decoding_options = DecodingOptions::test();
156157

157158
// All chunks except the last should be intermediate, the last should be final
158159
for (i, chunk) in chunks.iter().enumerate() {

lib/src/core/tests/comms.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ fn ack_data() -> Vec<u8> {
2424
#[test]
2525
pub fn hello() {
2626
let mut stream = Cursor::new(hello_data());
27-
let decoding_options = DecodingOptions::default();
27+
let decoding_options = DecodingOptions::test();
2828
let hello = HelloMessage::decode(&mut stream, &decoding_options).unwrap();
2929
println!("hello = {:?}", hello);
3030
assert_eq!(hello.message_header.message_type, MessageType::Hello);
@@ -43,7 +43,7 @@ pub fn hello() {
4343
#[test]
4444
pub fn acknowledge() {
4545
let mut stream = Cursor::new(ack_data());
46-
let decoding_options = DecodingOptions::default();
46+
let decoding_options = DecodingOptions::test();
4747
let ack = AcknowledgeMessage::decode(&mut stream, &decoding_options).unwrap();
4848
println!("ack = {:?}", ack);
4949
assert_eq!(ack.message_header.message_type, MessageType::Acknowledge);

lib/src/core/tests/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ where
3636
println!("encoded bytes = {:?}", actual);
3737
let mut stream = Cursor::new(actual);
3838

39-
let decoding_options = DecodingOptions::default();
39+
let decoding_options = DecodingOptions::test();
4040
let new_value: T = T::decode(&mut stream, &decoding_options).unwrap();
4141
println!("new value = {:?}", new_value);
4242
assert_eq!(value, new_value);

lib/src/server/builder.rs

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -308,36 +308,60 @@ impl ServerBuilder {
308308
}
309309

310310
/// Set the maximum number of subscriptions in a session
311-
pub fn max_subscriptions(mut self, max_subscriptions: u32) -> Self {
311+
pub fn max_subscriptions(mut self, max_subscriptions: usize) -> Self {
312312
self.config.limits.max_subscriptions = max_subscriptions;
313313
self
314314
}
315315

316316
/// Set the maximum number of monitored items per subscription
317-
pub fn max_monitored_items_per_sub(mut self, max_monitored_items_per_sub: u32) -> Self {
317+
pub fn max_monitored_items_per_sub(mut self, max_monitored_items_per_sub: usize) -> Self {
318318
self.config.limits.max_monitored_items_per_sub = max_monitored_items_per_sub;
319319
self
320320
}
321321

322322
/// Set the max array length in elements
323-
pub fn max_array_length(mut self, max_array_length: u32) -> Self {
323+
pub fn max_array_length(mut self, max_array_length: usize) -> Self {
324324
self.config.limits.max_array_length = max_array_length;
325325
self
326326
}
327327

328328
/// Set the max string length in characters, i.e. if you set max to 1000 characters, then with
329329
/// UTF-8 encoding potentially that's 4000 bytes.
330-
pub fn max_string_length(mut self, max_string_length: u32) -> Self {
330+
pub fn max_string_length(mut self, max_string_length: usize) -> Self {
331331
self.config.limits.max_string_length = max_string_length;
332332
self
333333
}
334334

335335
/// Set the max bytestring length in bytes
336-
pub fn max_byte_string_length(mut self, max_byte_string_length: u32) -> Self {
336+
pub fn max_byte_string_length(mut self, max_byte_string_length: usize) -> Self {
337337
self.config.limits.max_byte_string_length = max_byte_string_length;
338338
self
339339
}
340340

341+
/// Set the maximum message size
342+
pub fn max_message_size(mut self, max_message_size: usize) -> Self {
343+
self.config.limits.max_message_size = max_message_size;
344+
self
345+
}
346+
347+
/// Set the max chunk count
348+
pub fn max_chunk_count(mut self, max_chunk_count: usize) -> Self {
349+
self.config.limits.max_chunk_count = max_chunk_count;
350+
self
351+
}
352+
353+
// Set the send buffer size
354+
pub fn send_buffer_size(mut self, send_buffer_size: usize) -> Self {
355+
self.config.limits.send_buffer_size = send_buffer_size;
356+
self
357+
}
358+
359+
// Set the receive buffer size
360+
pub fn receive_buffer_size(mut self, receive_buffer_size: usize) -> Self {
361+
self.config.limits.receive_buffer_size = receive_buffer_size;
362+
self
363+
}
364+
341365
/// Sets the server to automatically trust client certs. This subverts the
342366
/// authentication during handshake, so only do this if you understand the risks.
343367
pub fn trust_client_certs(mut self) -> Self {

0 commit comments

Comments
 (0)