alexcrichton / alexcrichton/curl-rust

Use-after-free via `effective_url()` + `perform()` (soundness bug)

Abierto
#658 0 comentarios 0 reacciones 0 asignados Ver en GitHub
Lenguaje dominante
Rust
Estrellas
1.1k
Forks
258
Métricas de merge de PR
Sin PR fusionados en 30 d

Descripción

### Describe the bug

A use-after-free is reachable through purely safe code by holding a reference returned from `effective_url()` (or any other functions which call `getopt_str`/`getopt_bytes`) across a `perform()` call. After the second `perform()`, the reference reads freed memory.

AddressSanitizer confirms this is a heap-use-after-free.
The command is `RUSTFLAGS="-Z sanitizer=address" cargo run --example poc_effective_url_uaf`

ASAN output:
```
=================================================================
==3747821==ERROR: AddressSanitizer: heap-use-after-free on address 0x7288da7ff420 at pc 0x60c1ca0adef4 bp 0x7ffe16311100 sp 0x7ffe163108b8
READ of size 1 at 0x7288da7ff420 thread T0
#0 0x60c1ca0adef3 in memcpy /rustc/llvm/src/llvm-project/compiler-rt/lib/asan/../sanitizer_common/sanitizer_common_interceptors_memintrinsics.inc:115:5
#1 0x60c1ca1b6e2d in core::ptr::copy_nonoverlapping::h97acfe6ad30ebd17 /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/core/src/ptr/mod.rs:547:14
#2 0x60c1ca1b6e2d in std::io::buffered::bufwriter::BufWriter$LT$W$GT$::write_to_buffer_unchecked::h3f57a224160a7017 /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/std/src/io/buffered/bufwriter.rs:446:13
#3 0x60c1ca1b6e2d in _$LT$std..io..buffered..bufwriter..BufWriter$LT$W$GT$$u20$as$u20$std..io..Write$GT$::write_all::h876ab6fdfc5ce5b8 /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/std/src/io/buffered/bufwriter.rs:549:22
#4 0x60c1ca1b6e2d in _$LT$std..io..buffered..linewritershim..LineWriterShim$LT$W$GT$$u20$as$u20$std..io..Write$GT$::write_all::h521b1ff5ebebbf5d /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/std/src/io/buffered/linewritershim.rs
#5 0x60c1ca1b6e2d in _$LT$std..io..buffered..linewriter..LineWriter$LT$W$GT$$u20$as$u20$std..io..Write$GT$::write_all::h592b06df2e7a6065 /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/std/src/io/buffered/linewriter.rs:209:46
#6 0x60c1ca1b6e2d in _$LT$std..io..stdio..StdoutLock$u20$as$u20$std..io..Write$GT$::write_all::haab1b9685135a4f4 /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/std/src/io/stdio.rs:860:33
#7 0x60c1ca1b78d5 in _$LT$std..io..default_write_fmt..Adapter$LT$T$GT$$u20$as$u20$core..fmt..Write$GT$::write_str::h0140c20ed9c59ded /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/std/src/io/mod.rs:628:30
#8 0x60c1ca1d7013 in core::fmt::Formatter::write_str::h286a7b22baec06d8 /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/core/src/fmt/mod.rs:1886:18
#9 0x60c1ca1d7013 in _$LT$str$u20$as$u20$core..fmt..Debug$GT$::fmt::hc5209c6df06c8d33 /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/core/src/fmt/mod.rs:2735:23
#10 0x60c1ca0e7fb3 in _$LT$$RF$T$u20$as$u20$core..fmt..Debug$GT$::fmt::h4875a9f64db318db /home/tony/.rustup/toolchains/nightly-2025-08-06-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/fmt/mod.rs:2658:62
#11 0x60c1ca1d6d08 in core::fmt::builders::DebugTuple::field::_$u7b$$u7b$closure$u7d$$u7d$::h47968e5ad189eb8c /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/core/src/fmt/builders.rs:330:35
#12 0x60c1ca1d6d08 in core::fmt::builders::DebugTuple::field_with::_$u7b$$u7b$closure$u7d$$u7d$::hf8e723f4efacaade /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/core/src/fmt/builders.rs:355:17
#13 0x60c1ca1d6d08 in core::result::Result$LT$T$C$E$GT$::and_then::h83e14fc5f0ea04d1 /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/core/src/result.rs:1483:22
#14 0x60c1ca1d6d08 in core::fmt::builders::DebugTuple::field_with::h18a6cfe995292b14 /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/core/src/fmt/builders.rs:342:35
#15 0x60c1ca1d6d08 in core::fmt::builders::DebugTuple::field::h0e9715aca75ffc74 /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/core/src/fmt/builders.rs:330:14
#16 0x60c1ca1d6d08 in core::fmt::Formatter::debug_tuple_field1_finish::h1eb3034ec57245bc /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/core/src/fmt/mod.rs:2404:17
#17 0x60c1ca0ddcb2 in _$LT$core..option..Option$LT$T$GT$$u20$as$u20$core..fmt..Debug$GT$::fmt::h0b40fa495959b028 /home/tony/.rustup/toolchains/nightly-2025-08-06-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/option.rs:588:20
#18 0x60c1ca1d60a2 in core::fmt::rt::Argument::fmt::h8a0118be6af5db40 /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/core/src/fmt/rt.rs:173:76
#19 0x60c1ca1d60a2 in core::fmt::write::hca39e7f1c4949f2f /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/core/src/fmt/mod.rs:1468:25
#20 0x60c1ca1b6abd in std::io::default_write_fmt::h0eff647ecb0f018e /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/std/src/io/mod.rs:639:11
#21 0x60c1ca1b6abd in std::io::Write::write_fmt::hc9b62fc2891d9a4d /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/std/src/io/mod.rs:1954:13
#22 0x60c1ca1b6abd in _$LT$$RF$std..io..stdio..Stdout$u20$as$u20$std..io..Write$GT$::write_fmt::h6db882bd861efaa1 /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/std/src/io/stdio.rs:834:21
#23 0x60c1ca1b7421 in _$LT$std..io..stdio..Stdout$u20$as$u20$std..io..Write$GT$::write_fmt::h03b79f1369db63ce /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/std/src/io/stdio.rs:808:18
#24 0x60c1ca1b7421 in std::io::stdio::print_to::ha854b7705c7aa2ae /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/std/src/io/stdio.rs:1164:32
#25 0x60c1ca1b7421 in std::io::stdio::_print::h931a80cf29107f83 /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/std/src/io/stdio.rs:1275:5
#26 0x60c1ca0dd9e5 in poc_effective_url_uaf::main::h5649952c245e6a45 /home/tony/proj/tests/curl-rust/examples/poc_effective_url_uaf.rs:30:5
#27 0x60c1ca0ddd7a in core::ops::function::FnOnce::call_once::h5c1ddd6a872b35a4 /home/tony/.rustup/toolchains/nightly-2025-08-06-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:253:5
#28 0x60c1ca0dd17d in std::sys::backtrace::__rust_begin_short_backtrace::h3eec8b7dd0e6f950 /home/tony/.rustup/toolchains/nightly-2025-08-06-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys/backtrace.rs:158:18
#29 0x60c1ca0dd100 in std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::h171089efc686c4df /home/tony/.rustup/toolchains/nightly-2025-08-06-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:206:18
#30 0x60c1ca1b521f in core::ops::function::impls::_$LT$impl$u20$core..ops..function..FnOnce$LT$A$GT$$u20$for$u20$$RF$F$GT$::call_once::h99b89a21395e737e /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/core/src/ops/function.rs:290:21
#31 0x60c1ca1b521f in std::panicking::catch_unwind::do_call::h51fde413d96f01d7 /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/std/src/panicking.rs:589:40
#32 0x60c1ca1b521f in std::panicking::catch_unwind::h8f41973163f34070 /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/std/src/panicking.rs:552:19
#33 0x60c1ca1b521f in std::panic::catch_unwind::h442dcdbd746e6f4f /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/std/src/panic.rs:359:14
#34 0x60c1ca1b521f in std::rt::lang_start_internal::_$u7b$$u7b$closure$u7d$$u7d$::h5a036fb08f859f95 /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/std/src/rt.rs:175:24
#35 0x60c1ca1b521f in std::panicking::catch_unwind::do_call::h8300227963603c66 /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/std/src/panicking.rs:589:40
#36 0x60c1ca1b521f in std::panicking::catch_unwind::h7118510fdbd8d238 /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/std/src/panicking.rs:552:19
#37 0x60c1ca1b521f in std::panic::catch_unwind::hfb2e6b22c219a577 /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/std/src/panic.rs:359:14
#38 0x60c1ca1b521f in std::rt::lang_start_internal::h9fe5bed3f7fb1990 /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/std/src/rt.rs:171:5
#39 0x60c1ca0dd06f in std::rt::lang_start::hacb593e7b684c3a8 /home/tony/.rustup/toolchains/nightly-2025-08-06-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:205:5
#40 0x60c1ca0ddb1d in main (/home/tony/proj/tests/curl-rust/target/debug/examples/poc_effective_url_uaf+0x142b1d) (BuildId: 281813aa260f5ea20966c46d6f5a77cba7ea9dc9)
#41 0x7658db62a1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#42 0x7658db62a28a in __libc_start_main csu/../csu/libc-start.c:360:3
#43 0x60c1ca0237f4 in _start (/home/tony/proj/tests/curl-rust/target/debug/examples/poc_effective_url_uaf+0x887f4) (BuildId: 281813aa260f5ea20966c46d6f5a77cba7ea9dc9)

0x7288da7ff420 is located 0 bytes inside of 32-byte region [0x7288da7ff420,0x7288da7ff440)
freed by thread T0 here:
#0 0x60c1ca0aebf6 in free /rustc/llvm/src/llvm-project/compiler-rt/lib/asan/asan_malloc_linux.cpp:51:3
#1 0x60c1ca12ad25 in curl_free /home/tony/proj/tests/curl-rust/curl-sys/curl/lib/escape.c:192:3
#2 0x60c1ca128fbd in Curl_bufref_free /home/tony/proj/tests/curl-rust/curl-sys/curl/lib/bufref.c:61:5
#3 0x60c1ca12900b in Curl_bufref_set /home/tony/proj/tests/curl-rust/curl-sys/curl/lib/bufref.c:78:3
#4 0x60c1ca16c794 in Curl_pretransfer /home/tony/proj/tests/curl-rust/curl-sys/curl/lib/transfer.c:513:3
#5 0x60c1ca14ca47 in multi_runsingle /home/tony/proj/tests/curl-rust/curl-sys/curl/lib/multi.c:2520:16
#6 0x60c1ca14d2a8 in multi_perform /home/tony/proj/tests/curl-rust/curl-sys/curl/lib/multi.c:2803:17
#7 0x60c1ca14d4e1 in curl_multi_perform /home/tony/proj/tests/curl-rust/curl-sys/curl/lib/multi.c:2864:10
#8 0x60c1ca10504f in easy_transfer /home/tony/proj/tests/curl-rust/curl-sys/curl/lib/easy.c:694:17
#9 0x60c1ca1052de in easy_perform /home/tony/proj/tests/curl-rust/curl-sys/curl/lib/easy.c:802:42
#10 0x60c1ca105335 in curl_easy_perform /home/tony/proj/tests/curl-rust/curl-sys/curl/lib/easy.c:820:10
#11 0x60c1ca0ecff8 in curl::easy::handler::Easy2$LT$H$GT$::perform::ha0e3707963033325 /home/tony/proj/tests/curl-rust/src/easy/handler.rs:3200:37
#12 0x60c1ca0f2e4b in curl::easy::handle::Easy::do_perform::h7de409d500ce7573 /home/tony/proj/tests/curl-rust/src/easy/handle.rs:1406:20
#13 0x60c1ca0f2cec in curl::easy::handle::Easy::perform::h547399a98049c20a /home/tony/proj/tests/curl-rust/src/easy/handle.rs:1386:14
#14 0x60c1ca0dd96b in poc_effective_url_uaf::main::h5649952c245e6a45 /home/tony/proj/tests/curl-rust/examples/poc_effective_url_uaf.rs:28:5
#15 0x60c1ca0ddd7a in core::ops::function::FnOnce::call_once::h5c1ddd6a872b35a4 /home/tony/.rustup/toolchains/nightly-2025-08-06-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:253:5
#16 0x60c1ca0dd06f in std::rt::lang_start::hacb593e7b684c3a8 /home/tony/.rustup/toolchains/nightly-2025-08-06-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:205:5
#17 0x60c1ca0ddb1d in main (/home/tony/proj/tests/curl-rust/target/debug/examples/poc_effective_url_uaf+0x142b1d) (BuildId: 281813aa260f5ea20966c46d6f5a77cba7ea9dc9)
#18 0x7658db62a28a in __libc_start_main csu/../csu/libc-start.c:360:3
#19 0x60c1ca0237f4 in _start (/home/tony/proj/tests/curl-rust/target/debug/examples/poc_effective_url_uaf+0x887f4) (BuildId: 281813aa260f5ea20966c46d6f5a77cba7ea9dc9)

previously allocated by thread T0 here:
#0 0x60c1ca0af28c in realloc /rustc/llvm/src/llvm-project/compiler-rt/lib/asan/asan_malloc_linux.cpp:81:3
#1 0x60c1ca113dfb in dyn_nappend /home/tony/proj/tests/curl-rust/curl-sys/curl/lib/curlx/dynbuf.c:105:15
#2 0x60c1ca113f91 in curlx_dyn_addn /home/tony/proj/tests/curl-rust/curl-sys/curl/lib/curlx/dynbuf.c:167:10
#3 0x60c1ca147e42 in alloc_addbyter /home/tony/proj/tests/curl-rust/curl-sys/curl/lib/mprintf.c:1146:21
#4 0x60c1ca147470 in out_string /home/tony/proj/tests/curl-rust/curl-sys/curl/lib/mprintf.c:910:5
#5 0x60c1ca147a99 in formatf /home/tony/proj/tests/curl-rust/curl-sys/curl/lib/mprintf.c:1060:10
#6 0x60c1ca147f53 in curl_mvaprintf /home/tony/proj/tests/curl-rust/curl-sys/curl/lib/mprintf.c:1177:9
#7 0x60c1ca14806e in curl_maprintf /home/tony/proj/tests/curl-rust/curl-sys/curl/lib/mprintf.c:1192:7
#8 0x60c1ca17971b in urlget_url /home/tony/proj/tests/curl-rust/curl-sys/curl/lib/urlapi.c:1508:11
#9 0x60c1ca179a97 in curl_url_get /home/tony/proj/tests/curl-rust/curl-sys/curl/lib/urlapi.c:1616:12
#10 0x60c1ca17148b in parseurlandfillconn /home/tony/proj/tests/curl-rust/curl-sys/curl/lib/url.c:1574:10
#11 0x60c1ca174a4d in url_create_needle /home/tony/proj/tests/curl-rust/curl-sys/curl/lib/url.c:3050:12
#12 0x60c1ca174f71 in url_find_or_create_conn /home/tony/proj/tests/curl-rust/curl-sys/curl/lib/url.c:3229:12
#13 0x60c1ca175629 in Curl_connect /home/tony/proj/tests/curl-rust/curl-sys/curl/lib/url.c:3430:12
#14 0x60c1ca14c2e9 in state_connect /home/tony/proj/tests/curl-rust/curl-sys/curl/lib/multi.c:2312:21
#15 0x60c1ca14cb0e in multi_runsingle /home/tony/proj/tests/curl-rust/curl-sys/curl/lib/multi.c:2545:17
#16 0x60c1ca14d2a8 in multi_perform /home/tony/proj/tests/curl-rust/curl-sys/curl/lib/multi.c:2803:17
#17 0x60c1ca14d4e1 in curl_multi_perform /home/tony/proj/tests/curl-rust/curl-sys/curl/lib/multi.c:2864:10
#18 0x60c1ca10504f in easy_transfer /home/tony/proj/tests/curl-rust/curl-sys/curl/lib/easy.c:694:17
#19 0x60c1ca1052de in easy_perform /home/tony/proj/tests/curl-rust/curl-sys/curl/lib/easy.c:802:42
#20 0x60c1ca105335 in curl_easy_perform /home/tony/proj/tests/curl-rust/curl-sys/curl/lib/easy.c:820:10
#21 0x60c1ca0ecff8 in curl::easy::handler::Easy2$LT$H$GT$::perform::ha0e3707963033325 /home/tony/proj/tests/curl-rust/src/easy/handler.rs:3200:37
#22 0x60c1ca0f2e4b in curl::easy::handle::Easy::do_perform::h7de409d500ce7573 /home/tony/proj/tests/curl-rust/src/easy/handle.rs:1406:20
#23 0x60c1ca0f2cec in curl::easy::handle::Easy::perform::h547399a98049c20a /home/tony/proj/tests/curl-rust/src/easy/handle.rs:1386:14
#24 0x60c1ca0dd7a8 in poc_effective_url_uaf::main::h5649952c245e6a45 /home/tony/proj/tests/curl-rust/examples/poc_effective_url_uaf.rs:20:5
#25 0x60c1ca0ddd7a in core::ops::function::FnOnce::call_once::h5c1ddd6a872b35a4 /home/tony/.rustup/toolchains/nightly-2025-08-06-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:253:5
#26 0x60c1ca0dd06f in std::rt::lang_start::hacb593e7b684c3a8 /home/tony/.rustup/toolchains/nightly-2025-08-06-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:205:5
#27 0x60c1ca0ddb1d in main (/home/tony/proj/tests/curl-rust/target/debug/examples/poc_effective_url_uaf+0x142b1d) (BuildId: 281813aa260f5ea20966c46d6f5a77cba7ea9dc9)
#28 0x7658db62a28a in __libc_start_main csu/../csu/libc-start.c:360:3
#29 0x60c1ca0237f4 in _start (/home/tony/proj/tests/curl-rust/target/debug/examples/poc_effective_url_uaf+0x887f4) (BuildId: 281813aa260f5ea20966c46d6f5a77cba7ea9dc9)

SUMMARY: AddressSanitizer: heap-use-after-free /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/core/src/ptr/mod.rs:547:14 in core::ptr::copy_nonoverlapping::h97acfe6ad30ebd17
Shadow bytes around the buggy address:
0x7288da7ff180: 00 00 00 00 fa fa 00 00 00 00 fa fa fd fd fd fd
0x7288da7ff200: fa fa fd fd fd fd fa fa 00 00 05 fa fa fa 00 00
0x7288da7ff280: 02 fa fa fa fd fd fd fa fa fa 00 00 06 fa fa fa
0x7288da7ff300: fd fd fd fa fa fa 00 00 03 fa fa fa fd fd fd fa
0x7288da7ff380: fa fa 00 00 03 fa fa fa fd fd fd fd fa fa fd fd
=>0x7288da7ff400: fd fd fa fa[fd]fd fd fd fa fa fd fd fd fd fa fa
0x7288da7ff480: fd fd fd fd fa fa fd fd fd fa fa fa fd fd fd fa
0x7288da7ff500: fa fa 00 00 00 fa fa fa fd fd fd fd fa fa fd fd
0x7288da7ff580: fd fd fa fa 00 00 00 00 fa fa 00 00 00 00 fa fa
0x7288da7ff600: 00 00 00 00 fa fa 00 00 00 00 fa fa 00 00 00 00
0x7288da7ff680: fa fa fd fd fd fd fa fa fd fd fd fa fa fa fd fd
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==3747821==ABORTING
```

### Root cause

The ASAN trace has three sections (from bottom to top):

- **Section 1** ("previously allocated") — the first `perform()` (poc_effective_url_uaf.rs:20:5) allocates the URL buffer via `Curl_connect` -> `parseurlandfillconn` -> `curl_maprintf` -> `realloc`.
- **Section 2** ("freed") — the second `perform()` (poc_effective_url_uaf.rs:28:5) frees that buffer via `Curl_pretransfer` -> `Curl_bufref_set` -> `Curl_bufref_free` -> `free()`.
- **Section 3** ("READ of size 1") — user code reads the dangling `&str` (via `println!`), triggering the ASAN report.

Between Section 1 and Section 2, user code calls `easy.effective_url()` which returns `&str` pointing directly into the buffer allocated in Section 1. After Section 2 frees it, the `&str` becomes dangling.

**Why the borrow checker fails to prevent this:**

```rust
pub fn perform(&self) -> Result<(), Error> // shared borrow
pub fn effective_url(&self) -> Result, Error> // shared borrow, returns &str tied to &self
```

Both take `&self`. Rust allows multiple shared borrows to coexist:

```rust
let url_ref = easy.effective_url()?; // &str pointing into libcurl's heap buffer
easy.perform()?; // frees that buffer in Curl_pretransfer
println!("{}", url_ref); // reads freed memory
```

The type system assumes `&str` tied to `&self` remains valid while `&self` is live. But the buffer is owned by libcurl's C heap, and `perform()` unconditionally frees it.

### To reproduce

```rust
use curl::easy::Easy;

// #[inline(never)] ensures ASAN call stack shows distinct function names
// for the two perform() calls, making the trace unambiguous.
#[inline(never)]
fn first_perform(easy: &Easy) {
let _ = easy.perform();
}

#[inline(never)]
fn second_perform(easy: &Easy) {
let _ = easy.perform();
}

fn main() {
let mut easy = Easy::new();
easy.url("http://127.0.0.1:1").unwrap();

// First perform — allocates effective_url buffer inside libcurl
first_perform(&easy);

// Get reference to libcurl's internal buffer — &str lifetime tied to &easy
let url_ref = easy.effective_url().unwrap();
println!("Before second perform: {:?}", url_ref);

// Second perform — libcurl frees the buffer in Curl_pretransfer (unconditionally).
// perform() takes &self, so Rust allows this alongside the live url_ref borrow.
second_perform(&easy);

println!("After second perform: {:?}", url_ref); // uaf here
}
```

Run with:

```sh
RUSTFLAGS="-Z sanitizer=address"cargo run --example poc_effective_url_uaf
```

### Soundness bug
This is a soundness bug:
- The caller writes no `unsafe` code.
- The Rust type system should prevent use-after-free but actually failed.
- This violates Rust's safety guarantee that safe code must not be able to observe undefined behavior.

### Test environment

- Version: curl-rust master (commit 0cfd9e3b8b1aa0b8fc2c8d552597555a30a21416)
- OS: Ubuntu 24.04, x86_64
- Rustc: rustc 1.91.0-nightly (ec7c02612 2025-08-05)

### Suggested fix

For example, change `getopt_str`/`getopt_bytes` to return owned data, and change `effective_url` to return `Result, Error>` instead of `Result, Error>`. Other callers of `getopt_str`/`getopt_bytes` would also need to be updated accordingly.

```rust
fn getopt_bytes(&self, opt: curl_sys::CURLINFO) -> Result>, Error> { // return type: &[u8] -> Vec
unsafe {
let p = self.getopt_ptr(opt)?;
if p.is_null() {
Ok(None)
} else {
Ok(Some(CStr::from_ptr(p).to_bytes().to_vec())) // -> return owned Vec instead of borrowed &[u8]
}
}
}

fn getopt_str(&self, opt: curl_sys::CURLINFO) -> Result, Error> { // return type: &str -> String
match self.getopt_bytes(opt) {
Ok(None) => Ok(None),
Err(e) => Err(e),
Ok(Some(bytes)) => match String::from_utf8(bytes) { // str::from_utf8(bytes) -> String::from_utf8(bytes)
Ok(s) => Ok(Some(s)),
Err(_) => Err(Error::new(curl_sys::CURLE_CONV_FAILED)),
},
}
}

pub fn effective_url(&self)-> Result, Error> { // return type: &str -> String
self.getopt_str(curl_sys::CURLINFO_EFFECTIVE_URL)
}
```

Guía de contribución

No hay ninguna guía de contribución indexada para este repositorio

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.