Reject unreadable UCE source units
This commit is contained in:
parent
cd2fa163e6
commit
a288b539c6
@ -146,6 +146,12 @@ compiler error rather than serving the old unit indefinitely. The per-unit lock
|
||||
also keeps concurrent synchronous compilers from waiting across a transitive
|
||||
graph when a last complete artifact is available.
|
||||
|
||||
Before preprocessing, the compiler verifies that the worker can actually read
|
||||
the source. An unreadable path is a compile failure with a persisted diagnostic;
|
||||
it never becomes an apparently valid empty side module. Source signatures mark
|
||||
unreadable inputs, so correcting access invalidates that failure and permits a
|
||||
normal retry without changing signatures for ordinary readable files.
|
||||
|
||||
---
|
||||
|
||||
## 4. The workspace runtime
|
||||
@ -351,7 +357,9 @@ header free-functions are `inline`. The wasm backend exposes only declarations
|
||||
`scripts/test_dependency_invalidation.sh`. The latter changes a transitive
|
||||
`#load`, then replaces a warmed worker artifact while preserving its
|
||||
whole-second mtime to prove both compiler and worker caches invalidate it. It
|
||||
also sends 48 requests and asserts the observed worker PID set does not exceed
|
||||
also rejects an unreadable unit without publishing a wasm artifact, restores
|
||||
its permissions and proves the next CLI request compiles it, then sends 48
|
||||
requests and asserts the observed worker PID set does not exceed
|
||||
`WORKER_COUNT`, guarding against accidental reintroduction of request-count
|
||||
recycling.
|
||||
`scripts/test_cold_component_deadline.sh` separately compiles a deliberately
|
||||
|
||||
@ -54,6 +54,23 @@ if [[ "$(http_marker)" != *"dependency-marker-a"* ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '%s\n' 'CLI(Request& context) { print("readable-source-marker"); }' >"$source_dir/unreadable.uce"
|
||||
chmod 000 "$source_dir/unreadable.uce"
|
||||
if unreadable_output=$(scripts/uce-cli "/$test_name/unreadable.uce" 2>&1); then
|
||||
echo "unreadable source unexpectedly compiled: $unreadable_output" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$unreadable_output" != *"source file is not readable"* ]]; then
|
||||
echo "unreadable source did not report its actual compile error: $unreadable_output" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -e "$cache_dir/unreadable.uce.wasm" ]]; then
|
||||
echo "unreadable source published a wasm artifact" >&2
|
||||
exit 1
|
||||
fi
|
||||
chmod 644 "$source_dir/unreadable.uce"
|
||||
assert_marker unreadable readable-source-marker
|
||||
|
||||
sed -i 's/dependency-marker-a/dependency-marker-b/' "$source_dir/child.uce"
|
||||
started_at=$(date +%s%N)
|
||||
http_during_rebuild=$(http_marker)
|
||||
|
||||
@ -2,8 +2,10 @@
|
||||
#include "compiler-parser.h"
|
||||
#include "hash.h"
|
||||
#include <algorithm>
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fcntl.h>
|
||||
#include <mutex>
|
||||
@ -50,6 +52,7 @@ struct UnitSourceSignatureEntry
|
||||
u64 modified_ns = 0;
|
||||
u64 changed_ns = 0;
|
||||
u64 size = 0;
|
||||
bool readable = false;
|
||||
String content_hash;
|
||||
StringList loaded_paths;
|
||||
};
|
||||
@ -128,6 +131,18 @@ StringList compiler_unit_load_paths(String file_name, String content)
|
||||
return(paths);
|
||||
}
|
||||
|
||||
bool compiler_source_readable(String file_name, const struct stat& info, int* read_error = 0)
|
||||
{
|
||||
int error = 0;
|
||||
if((info.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0)
|
||||
error = EACCES;
|
||||
else if(access(file_name.c_str(), R_OK) != 0)
|
||||
error = errno;
|
||||
if(read_error)
|
||||
*read_error = error;
|
||||
return(error == 0);
|
||||
}
|
||||
|
||||
UnitSourceSignatureEntry compiler_unit_source_entry(String file_name)
|
||||
{
|
||||
struct stat info;
|
||||
@ -137,10 +152,11 @@ UnitSourceSignatureEntry compiler_unit_source_entry(String file_name)
|
||||
entry.modified_ns = (u64)info.st_mtim.tv_sec * 1000000000ull + (u64)info.st_mtim.tv_nsec;
|
||||
entry.changed_ns = (u64)info.st_ctim.tv_sec * 1000000000ull + (u64)info.st_ctim.tv_nsec;
|
||||
entry.size = (u64)info.st_size;
|
||||
entry.readable = compiler_source_readable(file_name, info);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(unit_source_signature_cache_mutex);
|
||||
auto cached = unit_source_signature_cache.find(file_name);
|
||||
if(cached != unit_source_signature_cache.end() && cached->second.modified_ns == entry.modified_ns && cached->second.changed_ns == entry.changed_ns && cached->second.size == entry.size)
|
||||
if(cached != unit_source_signature_cache.end() && cached->second.modified_ns == entry.modified_ns && cached->second.changed_ns == entry.changed_ns && cached->second.size == entry.size && cached->second.readable == entry.readable)
|
||||
return(cached->second);
|
||||
}
|
||||
String content = file_get_contents(file_name);
|
||||
@ -165,7 +181,7 @@ void compiler_append_unit_source_signature(String file_name, std::set<String>& v
|
||||
visited.insert(normalized);
|
||||
|
||||
UnitSourceSignatureEntry entry = compiler_unit_source_entry(normalized);
|
||||
signature += normalized + ":" + entry.content_hash + "\n";
|
||||
signature += normalized + ":" + entry.content_hash + (entry.readable ? String("") : String(":unreadable")) + "\n";
|
||||
for(String loaded : entry.loaded_paths)
|
||||
compiler_append_unit_source_signature(loaded, visited, signature);
|
||||
}
|
||||
@ -793,6 +809,22 @@ void compile_shared_unit(Request* context, SharedUnit* su)
|
||||
compiler_record_compile_result(su, time_precise() - comp_start, false, "missing_source", su->compiler_messages);
|
||||
return;
|
||||
}
|
||||
struct stat source_info;
|
||||
int read_error = 0;
|
||||
bool source_readable = stat(su->file_name.c_str(), &source_info) == 0 && compiler_source_readable(su->file_name, source_info, &read_error);
|
||||
if(!source_readable && read_error == 0)
|
||||
read_error = errno == 0 ? ENOENT : errno;
|
||||
if(!source_readable)
|
||||
{
|
||||
su->compiler_messages = "source file is not readable (" + su->file_name + "): " + std::strerror(read_error);
|
||||
file_put_contents(su->compile_output_file_name, su->compiler_messages + "\n");
|
||||
file_put_contents(su->wasm_check_file_name, su->compiler_messages + "\n");
|
||||
file_put_contents(su->meta_file_name, compiler_unit_metadata_text(context, su));
|
||||
file_unlink(su->wasm_name);
|
||||
compiler_record_compile_result(su, time_precise() - comp_start, false, "compile_error", su->compiler_messages);
|
||||
printf("%s \n", compiler_format_compile_failure(su, su->compiler_messages).c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
shell_exec("mkdir -p " + shell_escape(su->pre_path));
|
||||
file_put_contents(su->pre_path + "/" + su->pre_file_name, preprocess_shared_unit(context, su));
|
||||
|
||||
@ -438,8 +438,9 @@ int handle_cli_complete(FastCGIRequest& request)
|
||||
// W7e: compile a cold/stale unit on demand; native execution has
|
||||
// been removed, so a unit that still cannot be served by wasm is a
|
||||
// request failure instead of a fallback path.
|
||||
SharedUnit* cli_compile_state = 0;
|
||||
if(!wasm_backend_should_handle(request, cli_unit))
|
||||
get_shared_unit(&request, cli_unit);
|
||||
cli_compile_state = get_shared_unit(&request, cli_unit);
|
||||
if(wasm_backend_should_handle(request, cli_unit))
|
||||
{
|
||||
String wasm_error = wasm_backend_serve(request, cli_unit, "cli");
|
||||
@ -452,7 +453,11 @@ int handle_cli_complete(FastCGIRequest& request)
|
||||
else
|
||||
{
|
||||
request.set_status(500, "Internal Server Error");
|
||||
print("UCE CLI wasm unit unavailable after compile: ", script_filename, "\n");
|
||||
String compile_error = cli_compile_state ? first(cli_compile_state->compiler_messages, cli_compile_state->compile_error_status) : String("");
|
||||
print("UCE CLI wasm unit unavailable after compile: ", script_filename);
|
||||
if(compile_error != "")
|
||||
print(": ", compile_error);
|
||||
print("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -575,14 +580,16 @@ int handle_complete(FastCGIRequest& request) {
|
||||
// unit on demand — get_shared_unit() builds the .wasm side-module — and
|
||||
// recheck. Native execution has been removed, so a unit that still cannot
|
||||
// be served by wasm becomes a clean 500 request failure.
|
||||
SharedUnit* entry_compile_state = 0;
|
||||
auto wasm_ready = [&](const String& unit) -> bool {
|
||||
if(!wasm_backend_should_handle(request, unit))
|
||||
get_shared_unit(&request, unit);
|
||||
entry_compile_state = get_shared_unit(&request, unit);
|
||||
return(wasm_backend_should_handle(request, unit));
|
||||
};
|
||||
auto fail_wasm_unavailable = [&](const String& handler) {
|
||||
failure_title = "wasm unit unavailable after compile";
|
||||
failure_details = handler + " handler could not be served by wasm";
|
||||
String compile_error = entry_compile_state ? first(entry_compile_state->compiler_messages, entry_compile_state->compile_error_status) : String("");
|
||||
failure_details = compile_error != "" ? compile_error : handler + " handler could not be served by wasm";
|
||||
failure_trace = "source: " + request.params["SCRIPT_FILENAME"];
|
||||
};
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user