fix possible double free when requests get aborted

This commit is contained in:
ading2210 2024-07-10 23:52:11 -07:00
parent 11795d2772
commit 31e4e25f82
7 changed files with 41 additions and 8 deletions

View file

@ -1,5 +1,10 @@
# Libcurl.js Changelog:
## v0.6.9 (4/30/24):
- Fix a possible double free when requests get aborted
- Handle `ReadableStream` objects as the request payload
- Support the Clang AddressSanitizer
## v0.6.8 (4/30/24):
- Add support for relative URLs in HTTP sessions
- Better error handling for invalid URLs

View file

@ -59,8 +59,11 @@ sudo apt install make cmake emscripten autoconf automake libtool pkg-config wget
The build script will generate `client/out/libcurl.js` as well as `client/out/libcurl.mjs`, which is an ES6 module. You can supply the following arguments to the build script to control the build:
- `release` - Use all optimizations.
- `single_file` - Include the WASM binary in the outputted JS using base64.
- `asan` - Use the Clang AddressSanitizer to catch possible memory bugs
- `all` - Build twice, once normally, and once as a single file.
Note: non-release builds will have the `-dev` version suffix and ASan builds will have the `-asan` suffix.
## Javascript API:
### Importing the Library:

View file

@ -28,7 +28,7 @@ EXPORTED_FUNCS="${EXPORTED_FUNCS:1}"
#compile options
RUNTIME_METHODS="addFunction,removeFunction,allocate,ALLOC_NORMAL"
COMPILER_OPTIONS="-o $MODULE_FILE -lcurl -lwolfssl -lcjson -lz -lbrotlidec -lbrotlicommon -lnghttp2 -I $INCLUDE_DIR -L $LIB_DIR"
EMSCRIPTEN_OPTIONS="-lwebsocket.js -sENVIRONMENT=worker,web -sASSERTIONS=1 -sLLD_REPORT_UNDEFINED -sALLOW_TABLE_GROWTH -sALLOW_MEMORY_GROWTH -sEXPORTED_FUNCTIONS=$EXPORTED_FUNCS -sEXPORTED_RUNTIME_METHODS=$RUNTIME_METHODS"
EMSCRIPTEN_OPTIONS="-lwebsocket.js -sENVIRONMENT=worker,web -sASSERTIONS=1 -sLLD_REPORT_UNDEFINED -sALLOW_TABLE_GROWTH -sALLOW_MEMORY_GROWTH -sNO_EXIT_RUNTIME -sEXPORTED_FUNCTIONS=$EXPORTED_FUNCS -sEXPORTED_RUNTIME_METHODS=$RUNTIME_METHODS"
#clean output dir
rm -rf $OUT_DIR
@ -50,8 +50,13 @@ if [[ "$*" == *"release"* ]]; then
COMPILER_OPTIONS="-Oz -flto $COMPILER_OPTIONS"
echo "note: building with release optimizations"
else
COMPILER_OPTIONS="$COMPILER_OPTIONS --profiling -g"
EMSCRIPTEN_OPTIONS="$EMSCRIPTEN_OPTIONS -sSTACK_OVERFLOW_CHECK=2 -sSAFE_HEAP=1"
COMPILER_OPTIONS="$COMPILER_OPTIONS --profiling -g "
EMSCRIPTEN_OPTIONS="$EMSCRIPTEN_OPTIONS -sSTACK_OVERFLOW_CHECK=2"
fi
if [[ "$*" == *"asan"* ]]; then
COMPILER_OPTIONS="$COMPILER_OPTIONS -fsanitize=address"
echo "note: building with asan, performance will suffer"
fi
if [[ "$*" == *"single_file"* ]]; then
@ -78,6 +83,8 @@ rm $MODULE_FILE
#add version number and copyright notice
VERSION=$(cat package.json | jq -r '.version')
[[ "$*" != *"release"* ]] && VERSION="$VERSION-dev"
[[ "$*" == *"asan"* ]] && VERSION="$VERSION-asan"
sed -i "s/__library_version__/$VERSION/" $OUT_FILE
WISP_VERSION=$(cat $WISP_CLIENT/package.json | jq -r '.version')
sed -i "s/__wisp_version__/$WISP_VERSION/" $OUT_FILE

View file

@ -41,9 +41,9 @@ class HTTPSession extends CurlSession {
request_async(url, params, body) {
return new Promise((resolve, reject) => {
let http_handle;
let http_handle = null;
let body_ptr = null;
let headers_callback = (stream) => {
let response_json = c_func_str(_http_get_info, [http_handle]);
let response = this.constructor.create_response(stream, JSON.parse(response_json));
@ -59,6 +59,12 @@ class HTTPSession extends CurlSession {
_free(body_ptr);
body_ptr = null;
}
if (http_handle == null) {
//a race condition with aborting requests may lead to this state
//if the request gets cancelled right before it finishes normally, this function gets called twice
//fortunately, we can just return here to prevent anything bad from happening
return;
}
if (error > 0) {
error_msg(`Request "${url}" failed with error code ${error}: ${get_error_str(error)}`);
reject(`Request failed with error code ${error}: ${get_error_str(error)}`);
@ -70,6 +76,7 @@ class HTTPSession extends CurlSession {
reject("Request failed because redirects were disallowed.");
}
this.remove_request(http_handle);
http_handle = null;
}
body_ptr = body ? allocate_array(body) : null;
@ -94,9 +101,15 @@ class HTTPSession extends CurlSession {
params.headers = params.headers || Object.fromEntries(resource.headers);
params.method = params.method || resource.method;
}
else {
else if (typeof url === "string" || url instanceof String) {
url = (new URL(url, this.base_url)).href;
}
else if (url instanceof URL) {
url = url.href;
}
else {
url = "" + url;
}
let body = await this.constructor.create_options(params);
return await this.request_async(url, params, body);
}

View file

@ -166,6 +166,8 @@ class CurlSession {
}
catch (e) {
//the readable stream has been closed elsewhere, so cancel the request
if (aborted) return;
aborted = true;
if (e instanceof TypeError) {
end_callback(-1);
}

View file

@ -110,4 +110,7 @@ char* http_get_info(CURL* http_handle) {
cJSON_Delete(response_json);
return response_json_str;
}
}
//the address sanitizer falsely flags any malloc operation as a memory leak
const char* __asan_default_options() { return "detect_leaks=false"; }

View file

@ -1,6 +1,6 @@
{
"name": "libcurl.js",
"version": "0.6.8",
"version": "0.6.9",
"description": "An experimental port of libcurl to WebAssembly for use in the browser.",
"main": "libcurl.mjs",
"exports": {