-
Notifications
You must be signed in to change notification settings - Fork 58
Fix buffer overflow and nul-byte replacement in error handling #243
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
base: main
Are you sure you want to change the base?
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 |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| # Unreleased | ||
| - Add fixes for buffer overflow and incorrect char replacement | ||
|
|
||
| # 0.31.0 - 2025-08-29 | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
|
@@ -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; | ||
|
|
@@ -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 | ||
| .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() | ||
| } | ||
| } | ||
|
|
@@ -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', ""); | ||
|
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 agree, the previous code was wrong (the character '0' is not the null terminator).
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 | ||
| } | ||
| } | ||
|
|
||
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.
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?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.
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 thelibprojbuffer.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.
Sorry, you're right!