Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Unreleased
- Add fixes for buffer overflow and incorrect char replacement

# 0.31.0 - 2025-08-29

Expand Down
20 changes: 13 additions & 7 deletions src/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
/// **Note**: `error_string_max_size` is set to 128 by libproj.
// TODO: build some length checks for the errors that are stuffed into it
// This functionality based on https://github.com/OSGeo/PROJ/blob/master/src/networkfilemanager.cpp#L1675
use proj_sys::{proj_context_set_network_callbacks, PJ_CONTEXT, PROJ_NETWORK_HANDLE};
use proj_sys::{PJ_CONTEXT, PROJ_NETWORK_HANDLE, proj_context_set_network_callbacks};

use std::collections::HashMap;
use std::ffi::CString;
Expand All @@ -22,7 +22,7 @@ use std::os::raw::c_ulonglong;
use std::ptr::{self, NonNull};
use ureq::Agent;

use crate::proj::{ProjError, _string};
use crate::proj::{_string, ProjError};
use libc::c_char;
use libc::c_void;
use std::boxed::Box;
Expand Down Expand Up @@ -161,8 +161,11 @@ pub(crate) unsafe extern "C" fn network_open(
#[allow(clippy::ptr_as_ptr)]
Err(e) => {
let err_string = e.to_string();
out_error_string.copy_from_nonoverlapping(err_string.as_ptr().cast(), err_string.len());
out_error_string.add(err_string.len()).write(0);
let len = err_string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before talking about the new code - I'm a bit confused by the previous code.

Isn't this redundant with what already happens inside _network_open? Why are we "re-copying" the error text?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so? The error originates in Rust code (in _network_open()), and we check for an error, returning early to the facade if it does. I don't think we're re-copying anything: we're copying the stringified Rust error message into the libproj buffer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, you're right!

.len()
.min(error_string_max_size.saturating_sub(1));
out_error_string.copy_from_nonoverlapping(err_string.as_ptr().cast(), len);
out_error_string.add(len).write(0);
ptr::null_mut()
}
}
Expand Down Expand Up @@ -344,9 +347,12 @@ pub(crate) unsafe extern "C" fn network_read_range(
Err(e) => {
// The assumption here is that if 0 is returned, whatever error is in out_error_string is displayed by libproj
// since this isn't a conversion using CString, nul chars must be manually stripped
let err_string = e.to_string().replace('0', "nought");
out_error_string.copy_from_nonoverlapping(err_string.as_ptr().cast(), err_string.len());
out_error_string.add(err_string.len()).write(0);
let err_string = e.to_string().replace('\0', "");
Copy link
Member

@michaelkirk michaelkirk Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, the previous code was wrong (the character '0' is not the null terminator).

// since this isn't a conversion using CString, nul chars must be manually stripped

This error message is coming from rust code, so the purpose of this code is to detect the situation where some rust developer includes a NULL in their string output, right?

Rust strings can contain NULL (as many times as they want) but it seems absolutely unhinged to do so. If we do want to be protective in this way, I guess we should check on line 164 too?

Also (take it or leave it), could we use the CString handling? I think it'd look something like:

Err(e) => {
  let err_string = CString::new(e.to_string()).unwrap_or(c"Error while buildling error message: Unable to report error specifics".into());
  let bytes = &err_string.as_bytes_with_nul();
  let len = bytes.len().min(error_string_max_size);
  out_error_string.copy_from_nonoverlapping(bytes.as_ptr().cast(), len);
  0
}

Is it "better"? IDK. Interacting with C is such a bummer.

let len = err_string
.len()
.min(error_string_max_size.saturating_sub(1));
out_error_string.copy_from_nonoverlapping(err_string.as_ptr().cast(), len);
out_error_string.add(len).write(0);
0usize
}
}
Expand Down
Loading