ww: protect public install outputs

This commit is contained in:
2026-08-21 04:11:01 +09:00
parent ce91f6662c
commit 1b71250ad5
7 changed files with 1064 additions and 90 deletions

View File

@@ -1309,6 +1309,7 @@ struct sepproduct {
int directory_product;
int no_tests;
int build_action; /* loaded product retained in the action list */
int public_out;
int context;
int root;
int variant_root; /* retained single-unit root outside directory products */
@@ -4983,6 +4984,7 @@ struct septxnentry {
char *backup;
int had_old;
int installed;
int public_output;
};
struct septxn {
@@ -4991,7 +4993,8 @@ struct septxn {
};
static int
sep_txn_add(struct septxn *tx, const char *stage, const char *dst)
sep_txn_add_mode(struct septxn *tx, const char *stage, const char *dst,
int public_output)
{
if (strcmp(stage, dst) == 0) {
fprintf(stderr, "ww: transaction path collision: %s\n", dst);
@@ -5013,6 +5016,7 @@ sep_txn_add(struct septxn *tx, const char *stage, const char *dst)
e->stage = strdup(stage);
e->dst = strdup(dst);
e->backup = sep_sprintf("%s.wwtxn.%ld.old", dst, (long)getpid());
e->public_output = public_output;
if (e->stage == NULL || e->dst == NULL || e->backup == NULL) {
sep_fail_nomem();
free(e->backup); free(e->dst); free(e->stage);
@@ -5029,6 +5033,18 @@ sep_txn_add(struct septxn *tx, const char *stage, const char *dst)
return 0;
}
static int
sep_txn_add(struct septxn *tx, const char *stage, const char *dst)
{
return sep_txn_add_mode(tx, stage, dst, 0);
}
static int
sep_txn_add_public(struct septxn *tx, const char *stage, const char *dst)
{
return sep_txn_add_mode(tx, stage, dst, 1);
}
static void
sep_txn_discard(struct septxn *tx)
{
@@ -5049,8 +5065,87 @@ sep_txn_free(struct septxn *tx)
memset(tx, 0, sizeof *tx);
}
struct sep_output_magic {
const unsigned char *bytes;
size_t n;
};
#define SEP_OUTPUT_MAGIC(s) { (const unsigned char *)(s), sizeof(s) - 1 }
/* Go 1.26.5 work.objectMagic. WW interfaces are an additional public output
* kind and always begin with the compiler-owned module directive. */
static const struct sep_output_magic sep_output_magic[] = {
SEP_OUTPUT_MAGIC("!<arch>\n"),
SEP_OUTPUT_MAGIC("<bigaf>\n"),
SEP_OUTPUT_MAGIC("\x7f" "ELF"),
SEP_OUTPUT_MAGIC("\xfe\xed\xfa\xce"),
SEP_OUTPUT_MAGIC("\xfe\xed\xfa\xcf"),
SEP_OUTPUT_MAGIC("\xce\xfa\xed\xfe"),
SEP_OUTPUT_MAGIC("\xcf\xfa\xed\xfe"),
SEP_OUTPUT_MAGIC("\x4d\x5a\x90\x00\x03\x00"),
SEP_OUTPUT_MAGIC("\x4d\x5a\x78\x00\x01\x00"),
SEP_OUTPUT_MAGIC("\x00\x00\x01\xeb"),
SEP_OUTPUT_MAGIC("\x00\x00\x8a\x97"),
SEP_OUTPUT_MAGIC("\x00\x00\x06\x47"),
SEP_OUTPUT_MAGIC("\x00\x61\x73\x6d"),
SEP_OUTPUT_MAGIC("\x01\xdf"),
SEP_OUTPUT_MAGIC("\x01\xf7"),
SEP_OUTPUT_MAGIC("//ww:module "),
};
#undef SEP_OUTPUT_MAGIC
static int
sep_is_object_output(const char *path)
{
unsigned char buf[64] = {0};
int fd = open(path, O_RDONLY);
if (fd < 0) return 0;
size_t got = 0;
int bad = 0;
while (got < sizeof buf) {
ssize_t n = read(fd, buf + got, sizeof buf - got);
if (n > 0) got += (size_t)n;
else if (n == 0) break;
else if (errno != EINTR) { bad = 1; break; }
}
(void)close(fd);
if (bad) return 0;
for (size_t i = 0; i < nelem(sep_output_magic); i++)
if (got >= sep_output_magic[i].n
&& memcmp(buf, sep_output_magic[i].bytes,
sep_output_magic[i].n) == 0)
return 1;
return 0;
}
/* Pinned Go's checkDstOverwrite follows symlinks with stat and protects only
* directories and nonempty regular non-object files. Other file kinds remain
* eligible for the install operation itself to replace. */
static int
sep_check_dst_overwrite(const char *dst)
{
struct stat st;
if (stat(dst, &st) != 0) return 0;
if (S_ISDIR(st.st_mode)) {
fputs("ww: build output ", stderr);
sep_put_quoted(dst);
fputs(" already exists and is a directory\n", stderr);
return -1;
}
if (S_ISREG(st.st_mode) && st.st_size != 0
&& !sep_is_object_output(dst)) {
fputs("ww: build output ", stderr);
sep_put_quoted(dst);
fputs(" already exists and is not an object file\n", stderr);
return -1;
}
return 0;
}
/* One request-wide rollback group: producers and linkers finish first; only
* then are old destinations parked and all staged files installed. */
* then are public destinations checked, old destinations parked, and all
* staged files installed. */
static int
sep_txn_commit(struct septxn *tx)
{
@@ -5060,6 +5155,10 @@ sep_txn_commit(struct septxn *tx)
tx->v[i].stage);
goto rollback;
}
for (int i = 0; i < tx->n; i++)
if (tx->v[i].public_output
&& sep_check_dst_overwrite(tx->v[i].dst) != 0)
goto rollback;
for (int i = 0; i < tx->n; i++)
if (path_exists_nofollow(tx->v[i].backup) != 0) {
fprintf(stderr, "ww: transaction backup already exists: %s\n",
@@ -5476,6 +5575,52 @@ sep_mkdirs(const char *path, mode_t mode, struct sep_created_dirs *created)
return 0;
}
/* A retained running test is built and executed from request-private storage.
* Its Go-like install action runs only after that test succeeds, but reuses
* the same guarded publisher as build and compile-only test products. */
static int
sep_install_test_output(const char *stage, const char *dst)
{
char *install_stage = sep_sprintf("%s.install", stage);
if (install_stage == NULL) return 1;
if (strlen(install_stage) + 1 > (size_t)PATH_MAX
|| path_exists_nofollow(install_stage) != 0
|| copy_executable_stage(stage, install_stage) != 0) {
fputs("ww: cannot stage retained test output\n", stderr);
free(install_stage);
return 1;
}
char *parent = sep_parent_path(dst);
if (parent == NULL) {
(void)unlink(install_stage);
free(install_stage);
return 1;
}
struct sep_created_dirs created = {0};
if (sep_mkdirs(parent, 0777, &created) != 0) {
fprintf(stderr, "ww: cannot create test output directory %s\n",
parent);
free(parent);
(void)unlink(install_stage);
free(install_stage);
return 1;
}
free(parent);
struct septxn tx = {0};
if (sep_txn_add_public(&tx, install_stage, dst) != 0
|| sep_txn_commit(&tx) != 0) {
sep_txn_discard(&tx);
sep_txn_free(&tx);
sep_rollback_dirs(&created);
(void)unlink(install_stage);
free(install_stage);
return 1;
}
sep_txn_free(&tx);
free(install_stage);
return 0;
}
/* build_sep_plan — discover dependencies for every requested product in one
* package universe, compile the dependency-first union once, then link each
* root from its own complete reachable archive closure. The dependency-first
@@ -6713,11 +6858,14 @@ prepare_transaction:
}
for (int i = 0; i < nproducts; i++) {
if (products[i].stage_out != NULL
&& sep_txn_add(&tx, products[i].stage_out,
products[i].out) < 0)
&& (products[i].public_out
? sep_txn_add_public(&tx, products[i].stage_out,
products[i].out)
: sep_txn_add(&tx, products[i].stage_out,
products[i].out)) < 0)
goto request_fail;
if (products[i].stage_publish != NULL
&& sep_txn_add(&tx, products[i].stage_publish,
&& sep_txn_add_public(&tx, products[i].stage_publish,
products[i].publish) < 0)
goto request_fail;
if (products[i].stage_iface != NULL) {
@@ -6725,7 +6873,7 @@ prepare_transaction:
int on = snprintf(outiface, sizeof outiface, "%s.wwi",
products[i].out);
if (on < 0 || (size_t)on >= sizeof outiface
|| sep_txn_add(&tx, products[i].stage_iface,
|| sep_txn_add_public(&tx, products[i].stage_iface,
outiface) < 0)
goto request_fail;
}
@@ -6771,7 +6919,8 @@ build_one_sep(const char *src, int entry_is_dir, const char *root_identity,
const struct seplinkflags *linkflags, int publish_package,
int require_command, int is_test,
int root_variant, const char *test_package, int emit_asm,
int keepscratch, const char *workdir, const char *create_output_dir,
int public_output, int keepscratch, const char *workdir,
const char *create_output_dir,
const char *default_output_dir, int output_path_error)
{
char scratch[PATH_MAX] = {0};
@@ -6786,6 +6935,7 @@ build_one_sep(const char *src, int entry_is_dir, const char *root_identity,
.artifact = NULL,
.variant = root_variant,
.build_action = 1,
.public_out = public_output,
.root = -1,
.variant_root = -1,
.support = -1,
@@ -7225,7 +7375,7 @@ do_build(int argc, char **argv)
* library compilation still need request-private product paths. */
int rc = build_one_sep(resolved, is_dir, root_identity, tmp, tmp, incs,
&linkflags, 0, 0, 0, SEP_VARIANT_PRODUCTION, NULL,
emit_asm, 0, workdir, NULL, NULL, 0);
emit_asm, 0, 0, workdir, NULL, NULL, 0);
int cleanfail = 0;
if (unlink(tmp) != 0 && errno != ENOENT) {
fputs("ww: cannot remove temporary output\n", stderr);
@@ -7241,7 +7391,7 @@ do_build(int argc, char **argv)
}
int rc = build_one_sep(resolved, is_dir, root_identity, out, objstem, incs,
&linkflags, outflag[0] != '\0', 0, 0, SEP_VARIANT_PRODUCTION, NULL,
emit_asm, 1, workdir, create_output_dir, default_output_dir,
emit_asm, 1, 1, workdir, create_output_dir, default_output_dir,
output_path_error);
free(incs);
return rc;
@@ -7287,7 +7437,7 @@ do_run(int argc, char **argv)
const char *root_identity = !literal && is_dir ? src : NULL;
int buildrc = build_one_sep(resolved, is_dir, root_identity, tmp, tmp, incs,
&linkflags,
0, 1, 0, SEP_VARIANT_PRODUCTION, NULL, 0, 0, NULL, NULL, NULL, 0);
0, 1, 0, SEP_VARIANT_PRODUCTION, NULL, 0, 0, 0, NULL, NULL, NULL, 0);
free(incs);
if (buildrc != 0) {
if (unlink(tmp) != 0 && errno != ENOENT)
@@ -7334,6 +7484,9 @@ do_run(int argc, char **argv)
static int
do_test(int argc, char **argv)
{
if (argc == 3
&& strcmp(argv[0], "--ww-install-test-output") == 0)
return sep_install_test_output(argv[1], argv[2]);
const char *src = NULL;
struct sepproduct *products = NULL;
int nproducts = 0, productcap = 0;
@@ -7475,7 +7628,10 @@ do_test(int argc, char **argv)
const char *publish = argv[++i];
const char *status = argv[++i];
size_t pn = strlen(name);
int build_product = strcmp(kind, "build") == 0;
int build_product = strcmp(kind, "build") == 0
|| strcmp(kind, "build-public") == 0;
int public_build_product =
strcmp(kind, "build-public") == 0;
int test_product = strcmp(kind, "test") == 0;
int has_production = strcmp(production, "-") != 0;
int has_internal = strcmp(internal, "-") != 0;
@@ -7527,6 +7683,7 @@ do_test(int argc, char **argv)
products[nproducts].no_tests = test_product
&& !has_internal && !has_external;
products[nproducts].build_action = 1;
products[nproducts].public_out = public_build_product;
products[nproducts].root = -1;
products[nproducts].variant_root = -1;
products[nproducts].production_root = -1;
@@ -7788,9 +7945,12 @@ do_test(int argc, char **argv)
}
char tmpdir[PATH_MAX] = {0}, tmp[PATH_MAX];
const char *outp;
int owntmp = (!outstem[0] || discard_output) && !workdir[0];
if (outstem[0] && !discard_output) outp = outstem;
else if (workdir[0]) {
int retain_output = outstem[0] && !discard_output;
int deferred_install = retain_output && !compileonly && !emit_asm;
int owntmp = deferred_install
|| ((!outstem[0] || discard_output) && !workdir[0]);
if (retain_output && !deferred_install) outp = outstem;
else if (workdir[0] && !deferred_install) {
/* The workdir owns the persistent test binary the same
* way it owns the package artifacts. */
int tn = snprintf(tmp, sizeof tmp, "%s/main", workdir);
@@ -7816,7 +7976,8 @@ do_test(int argc, char **argv)
outstem[0] && !discard_output ? outstem : tmp,
incs, NULL, 0, 0, 1,
SEP_VARIANT_PRODUCTION, NULL, emit_asm,
outstem[0] && !discard_output ? 1 : 0, workdir, NULL, NULL, 0);
retain_output && !deferred_install, retain_output ? 1 : 0,
workdir, NULL, NULL, 0);
if (br != 0) {
if (owntmp && unlink(outp) != 0 && errno != ENOENT)
fputs("ww: cannot remove temporary output\n", stderr);
@@ -7837,6 +7998,9 @@ do_test(int argc, char **argv)
return cleanfail ? 1 : 0;
}
int rc = run_test_bin(outp, pattern);
if (rc == 0 && deferred_install
&& sep_install_test_output(outp, outstem) != 0)
rc = 1;
if (owntmp && unlink(outp) != 0 && errno != ENOENT) {
fputs("ww: cannot remove temporary output\n", stderr);
if (rc == 0) rc = 1;
@@ -7860,9 +8024,12 @@ do_test(int argc, char **argv)
}
char tmpdir[PATH_MAX] = {0}, tmp[PATH_MAX];
const char *outp;
int owntmp = (!outstem[0] || discard_output) && !workdir[0];
if (outstem[0] && !discard_output) outp = outstem;
else if (workdir[0]) {
int retain_output = outstem[0] && !discard_output;
int deferred_install = retain_output && !compileonly && !emit_asm;
int owntmp = deferred_install
|| ((!outstem[0] || discard_output) && !workdir[0]);
if (retain_output && !deferred_install) outp = outstem;
else if (workdir[0] && !deferred_install) {
int tn = snprintf(tmp, sizeof tmp, "%s/main", workdir);
if (tn < 0 || (size_t)tn >= sizeof tmp) {
fputs("ww: workdir path is too long\n", stderr);
@@ -7884,7 +8051,8 @@ do_test(int argc, char **argv)
int br = build_one_sep(target, 0, NULL, outp,
outstem[0] && !discard_output ? outstem : tmp,
incs, NULL, 0, 0, 1, SEP_VARIANT_PRODUCTION, NULL, emit_asm,
outstem[0] && !discard_output ? 1 : 0, workdir, NULL, NULL, 0);
retain_output && !deferred_install, retain_output ? 1 : 0,
workdir, NULL, NULL, 0);
if (br != 0) {
if (owntmp && unlink(outp) != 0 && errno != ENOENT)
fputs("ww: cannot remove temporary output\n", stderr);
@@ -7905,6 +8073,9 @@ do_test(int argc, char **argv)
return cleanfail ? 1 : 0;
}
int rc = run_test_bin(outp, pattern);
if (rc == 0 && deferred_install
&& sep_install_test_output(outp, outstem) != 0)
rc = 1;
if (owntmp && unlink(outp) != 0 && errno != ENOENT) {
fputs("ww: cannot remove temporary output\n", stderr);
if (rc == 0) rc = 1;