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