|
11 | 11 | #include "node_profiling.h" |
12 | 12 | #include "node_snapshot_builder.h" |
13 | 13 | #include "permission/permission.h" |
| 14 | +#include "path.h" |
14 | 15 | #include "util-inl.h" |
15 | 16 | #include "v8-cppgc.h" |
16 | 17 | #include "v8-profiler.h" |
@@ -503,6 +504,224 @@ Worker::~Worker() { |
503 | 504 | Debug(this, "Worker %llu destroyed", thread_id_.id); |
504 | 505 | } |
505 | 506 |
|
| 507 | + |
| 508 | + |
| 509 | +// SEMVER-MAJOR: Permission ceiling for Worker when execArgv is explicit |
| 510 | +// (including []). Default Worker (no execArgv) is unchanged. |
| 511 | + |
| 512 | +namespace { |
| 513 | + |
| 514 | +#define PERMISSION_BOOL_FLAGS(V) \ |
| 515 | + V(allow_addons, "--allow-addons") \ |
| 516 | + V(allow_inspector, "--allow-inspector") \ |
| 517 | + V(allow_child_process, "--allow-child-process") \ |
| 518 | + V(allow_net, "--allow-net") \ |
| 519 | + V(allow_wasi, "--allow-wasi") \ |
| 520 | + V(allow_ffi, "--allow-ffi") \ |
| 521 | + V(allow_openssl_store, "--allow-openssl-store") \ |
| 522 | + V(allow_worker_threads, "--allow-worker") |
| 523 | + |
| 524 | +bool WorkerConfiguredPermission(const EnvironmentOptions* w) { |
| 525 | + if (w == nullptr) return false; |
| 526 | + if (w->permission || w->permission_audit) return true; |
| 527 | + if (!w->allow_fs_read.empty() || !w->allow_fs_write.empty()) return true; |
| 528 | +#define V(field, flag) || w->field |
| 529 | + return false PERMISSION_BOOL_FLAGS(V); |
| 530 | +#undef V |
| 531 | +} |
| 532 | + |
| 533 | +void ApplyParentPermissionCeiling(EnvironmentOptions* w, |
| 534 | + const EnvironmentOptions* parent) { |
| 535 | + w->permission = true; |
| 536 | + w->permission_audit = parent->permission_audit; |
| 537 | +#define V(field, flag) w->field = parent->field; |
| 538 | + PERMISSION_BOOL_FLAGS(V) |
| 539 | +#undef V |
| 540 | + w->allow_fs_read = parent->allow_fs_read; |
| 541 | + w->allow_fs_write = parent->allow_fs_write; |
| 542 | +} |
| 543 | + |
| 544 | +void NormalizePathForCompare(std::string* s) { |
| 545 | + while (s->size() > 1 && |
| 546 | + (s->back() == '/' || s->back() == static_cast<char>(92))) { |
| 547 | + s->pop_back(); |
| 548 | + } |
| 549 | +#ifdef _WIN32 |
| 550 | + for (char& c : *s) { |
| 551 | + if (c >= 'A' && c <= 'Z') { |
| 552 | + c = static_cast<char>(c - 'A' + 'a'); |
| 553 | + } |
| 554 | + if (c == '/') c = static_cast<char>(92); |
| 555 | + } |
| 556 | +#endif |
| 557 | +} |
| 558 | + |
| 559 | +std::string ResolveForCompare(Environment* env, const std::string& in) { |
| 560 | + if (in.empty() || in == "*") return in; |
| 561 | + std::string resolved = |
| 562 | + PathResolve(env, std::vector<std::string_view>{std::string_view(in)}); |
| 563 | + if (resolved.empty()) resolved = in; |
| 564 | + NormalizePathForCompare(&resolved); |
| 565 | + return resolved; |
| 566 | +} |
| 567 | + |
| 568 | +bool ParentEntryCoversResolvedPath(Environment* env, |
| 569 | + const std::string& parent_raw, |
| 570 | + const std::string& resolved_requested) { |
| 571 | + if (parent_raw == "*") return true; |
| 572 | + const std::string parent = ResolveForCompare(env, parent_raw); |
| 573 | + if (parent.empty()) return false; |
| 574 | + if (resolved_requested == parent) return true; |
| 575 | + if (resolved_requested.size() <= parent.size()) return false; |
| 576 | + if (resolved_requested.compare(0, parent.size(), parent) != 0) return false; |
| 577 | + const char next = resolved_requested[parent.size()]; |
| 578 | + return next == '/' || next == static_cast<char>(92); |
| 579 | +} |
| 580 | + |
| 581 | +bool ParentListHasWildcard(const std::vector<std::string>& parent) { |
| 582 | + for (const std::string& entry : parent) { |
| 583 | + if (entry == "*") return true; |
| 584 | + } |
| 585 | + return false; |
| 586 | +} |
| 587 | + |
| 588 | +void FilterPathListToParentSubset(Environment* env, |
| 589 | + EnvironmentOptions* w, |
| 590 | + std::vector<std::string>* worker, |
| 591 | + const std::vector<std::string>& parent) { |
| 592 | + if (worker == nullptr) return; |
| 593 | + if (worker->empty()) return; |
| 594 | + if (ParentListHasWildcard(parent)) return; |
| 595 | + |
| 596 | + std::vector<std::string> out; |
| 597 | + out.reserve(worker->size()); |
| 598 | + bool saw_star = false; |
| 599 | + for (const std::string& wpath : *worker) { |
| 600 | + if (wpath == "*") { |
| 601 | + saw_star = true; |
| 602 | + continue; |
| 603 | + } |
| 604 | + const std::string resolved_wpath = ResolveForCompare(env, wpath); |
| 605 | + for (const std::string& entry : parent) { |
| 606 | + if (ParentEntryCoversResolvedPath(env, entry, resolved_wpath)) { |
| 607 | + out.push_back(resolved_wpath); |
| 608 | + break; |
| 609 | + } |
| 610 | + } |
| 611 | + } |
| 612 | + if (saw_star) { |
| 613 | + *worker = parent; |
| 614 | + return; |
| 615 | + } |
| 616 | + *worker = std::move(out); |
| 617 | +} |
| 618 | + |
| 619 | +void IntersectPermissionGrants(Environment* env, |
| 620 | + EnvironmentOptions* w, |
| 621 | + const EnvironmentOptions* parent) { |
| 622 | + w->permission = true; |
| 623 | + w->permission_audit = w->permission_audit || parent->permission_audit; |
| 624 | +#define V(field, flag) w->field = w->field && parent->field; |
| 625 | + PERMISSION_BOOL_FLAGS(V) |
| 626 | +#undef V |
| 627 | + FilterPathListToParentSubset(env, w, &w->allow_fs_read, parent->allow_fs_read); |
| 628 | + FilterPathListToParentSubset( |
| 629 | + env, w, &w->allow_fs_write, parent->allow_fs_write); |
| 630 | +} |
| 631 | + |
| 632 | +void ClampWorkerPermissionToParent(Environment* env, |
| 633 | + PerIsolateOptions* worker_opts) { |
| 634 | + if (worker_opts == nullptr || env == nullptr || |
| 635 | + !env->permission()->enabled()) { |
| 636 | + return; |
| 637 | + } |
| 638 | + EnvironmentOptions* parent = |
| 639 | + env->isolate_data()->options()->get_per_env_options(); |
| 640 | + EnvironmentOptions* w = worker_opts->get_per_env_options(); |
| 641 | + if (parent == nullptr || w == nullptr) return; |
| 642 | + |
| 643 | + if (!WorkerConfiguredPermission(w)) { |
| 644 | + ApplyParentPermissionCeiling(w, parent); |
| 645 | + } else { |
| 646 | + IntersectPermissionGrants(env, w, parent); |
| 647 | + } |
| 648 | +} |
| 649 | + |
| 650 | +bool IsPermissionCliToken(const std::string& a) { |
| 651 | + if (a == "--permission" || a == "--permission-audit") return true; |
| 652 | + if (a == "--allow-fs-read" || a == "--allow-fs-write") return true; |
| 653 | + if (a.rfind("--allow-fs-read=", 0) == 0) return true; |
| 654 | + if (a.rfind("--allow-fs-write=", 0) == 0) return true; |
| 655 | +#define V(field, flag) \ |
| 656 | + if (a == flag) return true; \ |
| 657 | + { \ |
| 658 | + const size_t n = sizeof(flag) - 1; \ |
| 659 | + if (a.size() > n && a.compare(0, n, flag) == 0 && a[n] == '=') \ |
| 660 | + return true; \ |
| 661 | + } |
| 662 | + PERMISSION_BOOL_FLAGS(V) |
| 663 | +#undef V |
| 664 | + return false; |
| 665 | +} |
| 666 | + |
| 667 | +bool PermissionFlagTakesNextArg(const std::string& a) { |
| 668 | + return a == "--allow-fs-read" || a == "--allow-fs-write"; |
| 669 | +} |
| 670 | + |
| 671 | +bool PathSafeForAllowFlag(const std::string& path) { |
| 672 | + if (path.empty()) return false; |
| 673 | + for (unsigned char c : path) { |
| 674 | + if (c == 0 || c == 10 || c == 13) return false; |
| 675 | + } |
| 676 | + return true; |
| 677 | +} |
| 678 | + |
| 679 | +void RebuildExecArgvOutFromPermissionOptions( |
| 680 | + PerIsolateOptions* worker_opts, std::vector<std::string>* exec_argv_out) { |
| 681 | + if (worker_opts == nullptr || exec_argv_out == nullptr) return; |
| 682 | + EnvironmentOptions* w = worker_opts->get_per_env_options(); |
| 683 | + if (w == nullptr || !w->permission) return; |
| 684 | + |
| 685 | + std::vector<std::string> kept; |
| 686 | + kept.reserve(exec_argv_out->size()); |
| 687 | + for (size_t i = 0; i < exec_argv_out->size(); ++i) { |
| 688 | + const std::string& tok = (*exec_argv_out)[i]; |
| 689 | + if (tok.empty()) continue; |
| 690 | + if (IsPermissionCliToken(tok)) { |
| 691 | + if (PermissionFlagTakesNextArg(tok) && i + 1 < exec_argv_out->size()) { |
| 692 | + ++i; |
| 693 | + } |
| 694 | + continue; |
| 695 | + } |
| 696 | + kept.push_back(tok); |
| 697 | + } |
| 698 | + |
| 699 | + std::vector<std::string> out; |
| 700 | + out.reserve(kept.size() + 16 + w->allow_fs_read.size() + |
| 701 | + w->allow_fs_write.size()); |
| 702 | + for (const std::string& tok : kept) out.push_back(tok); |
| 703 | + |
| 704 | + out.push_back("--permission"); |
| 705 | + if (w->permission_audit) out.push_back("--permission-audit"); |
| 706 | +#define V(field, flag) \ |
| 707 | + if (w->field) out.push_back(flag); |
| 708 | + PERMISSION_BOOL_FLAGS(V) |
| 709 | +#undef V |
| 710 | + for (const std::string& p : w->allow_fs_read) { |
| 711 | + if (!PathSafeForAllowFlag(p)) continue; |
| 712 | + out.push_back("--allow-fs-read=" + p); |
| 713 | + } |
| 714 | + for (const std::string& p : w->allow_fs_write) { |
| 715 | + if (!PathSafeForAllowFlag(p)) continue; |
| 716 | + out.push_back("--allow-fs-write=" + p); |
| 717 | + } |
| 718 | + *exec_argv_out = std::move(out); |
| 719 | +} |
| 720 | + |
| 721 | +#undef PERMISSION_BOOL_FLAGS |
| 722 | + |
| 723 | +} // namespace |
| 724 | + |
506 | 725 | void Worker::New(const FunctionCallbackInfo<Value>& args) { |
507 | 726 | Environment* env = Environment::GetCurrent(args); |
508 | 727 | THROW_IF_INSUFFICIENT_PERMISSIONS( |
@@ -682,6 +901,14 @@ void Worker::New(const FunctionCallbackInfo<Value>& args) { |
682 | 901 | per_isolate_opts = env->isolate_data()->options()->Clone(); |
683 | 902 | } |
684 | 903 |
|
| 904 | + // Explicit execArgv only (including []). Default Worker path unchanged. |
| 905 | + if (env->permission()->enabled() && per_isolate_opts && |
| 906 | + args[2]->IsArray()) { |
| 907 | + ClampWorkerPermissionToParent(env, per_isolate_opts.get()); |
| 908 | + RebuildExecArgvOutFromPermissionOptions(per_isolate_opts.get(), |
| 909 | + &exec_argv_out); |
| 910 | + } |
| 911 | + |
685 | 912 | // Internal workers should not wait for inspector frontend to connect or |
686 | 913 | // break on the first line of internal scripts. Module loader threads are |
687 | 914 | // essential to load user codes and must not be blocked by the inspector |
|
0 commit comments