ww: protect public install outputs
This commit is contained in:
209
cmd/ww/main.c
209
cmd/ww/main.c
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user