-
Notifications
You must be signed in to change notification settings - Fork 238
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
curl::multi::Multi will make double free memory problem when some ssl error occurred #421
Comments
Can you say how you got or installed libcurl 7.80.0-DEV? |
Build from source:
I found that if remove the http2 support, it won't crash, and also I can't reproduce this problem on my Mac and Windows, maybe I should compare the code of libcurl-7.73.0 and libcurl 7.80.0-DEV. |
You're right, it does seem that the curl docs recommend removing all easy handles from a multi handle first before freeing the multi handle: https://curl.se/libcurl/c/curl_multi_cleanup.html. Though it is unclear if this is related to this issue or not, or if the way we're currently doing it is strictly incorrect. I also wouldn't rule out the possibility that this is an upstream issue in either curl or OpenSSL. I'll see if I am able to reproduce and if so, whether removing all handles first corrects the problem. |
So far I am not able to reproduce this on my local machine or inside a Centos 7.3.1611 Docker container. Are you also building OpenSSL and nghttp2 yourself? I see you are pointing to nonstandard paths for those. It would be helpful to see how you are building those. It might also help to strip out the superfluous parts in your example program, I've rewritten it down to the following. Could you verify that the below program also crashes for you with a double-free? I tried to isolate specifically the scenario you described (error from OpenSSL + dropping multi handle without removing easy handles): use curl::{
easy::{Easy, HttpVersion},
multi::Multi,
};
use std::time::Duration;
fn main() {
let multi = Multi::new();
let mut handles = Vec::new();
for _ in 0..2 {
let mut easy = Easy::new();
easy.url("https://self-signed.badssl.com").unwrap();
easy.http_version(HttpVersion::V2).unwrap();
handles.push(multi.add(easy).unwrap());
}
while multi.perform().unwrap() > 0 {
multi.wait(&mut [], Duration::from_secs(5)).unwrap();
}
drop(multi);
println!("Multi handle freed");
drop(handles);
println!("Easy handles freed");
} |
Sorry for my lack of rigor. Your version don't crash on my machine, so I do some modify and finally it crashed:
I also try to build it with mesalink backend, it crash too, but I copy the build result to other machine which has differece network env it won't crash, it may be network IO related. |
Thanks, having a minimal repro will make it easier to identify the problem and apply the appropriate fix. Its interesting that dropping the easy handles first is part of the issue, since |
It seem there is "safe guard" when cleanup, all I know now is double free this pointer and it‘s not because of duplicate conn_free call. I will do more debug later. |
Set a http proxy to easy handle will reproduce this preblem, on my machine it's setting by the |
@iiibui Thanks, that did the trick, I can consistently reproduce the double-free crash now as well. I'll work on stripping down the repro program further and hopefully identify the cause of why |
I think this might be an upstream curl bug. Using git bisect I was able to identify that this starts happening after this commit, which definitely looks like it could be related to this sort of problem: curl/curl@51c0ebc. I will work on translating the repro into a C program so we can open a ticket upstream. In the meantime, @iiibui could you verify my findings? If you build libcurl at commit For reference, I am able to consistently reproduce a double free now with the following program: use curl::{easy::Easy, multi::Multi};
use std::{
io::{copy, BufRead, BufReader, Write},
net::{SocketAddr, TcpListener, TcpStream},
thread,
};
fn main() {
let proxy_addr = spawn_http_proxy();
let multi = Multi::new();
let mut easy = Easy::new();
easy.url("https://self-signed.badssl.com").unwrap();
easy.proxy(&format!("http://{}", proxy_addr)).unwrap();
let easy = multi.add(easy).unwrap();
multi.perform().unwrap();
drop(easy);
println!("Easy handle freed");
drop(multi);
println!("Multi handle freed");
}
/// Spawn a simple HTTP proxy in a background thread for curl to talk to. Really
/// inefficient with threads but also very simple.
fn spawn_http_proxy() -> SocketAddr {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
thread::spawn(move || loop {
let (mut client, _) = listener.accept().unwrap();
thread::spawn(move || {
let mut reader = BufReader::new(client.try_clone().unwrap());
let mut request_header = String::new();
while !request_header.contains("\r\n\r\n") {
reader.read_line(&mut request_header).unwrap();
}
client.write_all(b"HTTP/1.1 200 OK\r\n\r\n").unwrap();
let upstream_addr = request_header.split(' ').nth(1).unwrap();
let mut upstream_reader = TcpStream::connect(upstream_addr).unwrap();
let mut upstream_writer = upstream_reader.try_clone().unwrap();
thread::spawn(move || {
let _ = copy(&mut reader, &mut upstream_writer);
});
let _ = copy(&mut upstream_reader, &mut client);
});
});
addr
} |
Upstream issue opened: curl/curl#7982 |
Yes, with commit |
Add a drop handler that guarantees that `curl_multi_remove_handle` is always called on easy handles added to a multi handle before the easy handle is dropped. Based on curl/curl#7982, we should not be allowing users to drop a previously attached handle without first calling `curl_multi_remove_handle`. In curl versions between 7.77 and 7.80 this could cause a crash under certain circumstances. While this will likely be fixed in the next curl version, it is still recommended to not allow this, plus we must still handle this case for users who may not have updated to a patched libcurl. I'm not totally happy with how this is implemented as it adds an `Arc::clone` call every time an easy handle is added to a multi handle, but I couldn't think of a better way of guaranteeing this behavior without breaking API changes. Fixes #421.
Add a drop handler that guarantees that `curl_multi_remove_handle` is always called on easy handles added to a multi handle before the easy handle is dropped. Based on curl/curl#7982, we should not be allowing users to drop a previously attached handle without first calling `curl_multi_remove_handle`. In curl versions between 7.77 and 7.80 this could cause a crash under certain circumstances. While this will likely be fixed in the next curl version, it is still recommended to not allow this, plus we must still handle this case for users who may not have updated to a patched libcurl. I'm not totally happy with how this is implemented as it adds an `Arc::clone` call every time an easy handle is added to a multi handle, but I couldn't think of a better way of guaranteeing this behavior without breaking API changes. Fixes #421.
The fix for this is now available in curl 0.4.41. |
Thanks. @ehuss I rebuild cargo 22ff7ac47c0e3a366637643c9cf38c61d649c10b which the rust-curl dep is aready updated to 0.4.41, I cannot reproduce the double-free crash on my machine now. |
Problem
OS: CentOS Linux release 7.3.1611 (Core)
Use curl::multi::Multi to fetch more than one https url will cause the program to crash when some ssl error occurred(rel issue):
Rust code
The url https://127.0.0.1:433 in the code is a service implemented by the simple golang code:
server.crt is a self signed certificate generated by OpenSSL, you can replace with your version.
Cargo.toml
curl -V:
The text was updated successfully, but these errors were encountered: