স্যান্ডবক্স 2 দিয়ে শুরু করা

এই পৃষ্ঠায়, আপনি শিখবেন কিভাবে স্যান্ডবক্স 2 দিয়ে আপনার নিজস্ব স্যান্ডবক্সড পরিবেশ তৈরি করবেন। আপনি শিখবেন কিভাবে একটি স্যান্ডবক্স নীতি সংজ্ঞায়িত করতে হয়, এবং কিছু উন্নত, কিন্তু সাধারণ, টুইক। হেডার ফাইলে উদাহরণ এবং কোড ডকুমেন্টেশনের পাশাপাশি গাইড হিসেবে এখানে তথ্য ব্যবহার করুন।

1. একটি স্যান্ডবক্স এক্সিকিউটর পদ্ধতি বেছে নিন

স্যান্ডবক্সিং একজন নির্বাহক দিয়ে শুরু হয় ( স্যান্ডবক্স এক্সিকিউটর দেখুন), যেটি স্যান্ডবক্সী চালানোর জন্য দায়ী। executor.h হেডার ফাইলে এই উদ্দেশ্যে প্রয়োজনীয় API রয়েছে। এপিআই খুবই নমনীয় এবং আপনার ব্যবহারের ক্ষেত্রে কোনটি সবচেয়ে ভালো কাজ করে তা বেছে নিতে দেয়। নিম্নলিখিত বিভাগগুলি 3টি ভিন্ন পদ্ধতি বর্ণনা করে যা থেকে আপনি চয়ন করতে পারেন৷

পদ্ধতি 1: একাকী - স্যান্ডবক্সিং ইতিমধ্যে সক্ষম করে একটি বাইনারি চালান

এটি স্যান্ডবক্সিং ব্যবহার করার সবচেয়ে সহজ উপায় এবং যখন আপনি একটি সম্পূর্ণ বাইনারি স্যান্ডবক্স করতে চান যার জন্য আপনার কাছে কোনও উত্স কোড নেই তখন এটি প্রস্তাবিত পদ্ধতি৷ এটি স্যান্ডবক্সিং ব্যবহার করার সবচেয়ে নিরাপদ উপায়, কারণ সেখানে কোনো আনস্যান্ডবক্সিং ইনিশিয়ালাইজেশন নেই যা বিরূপ প্রভাব ফেলতে পারে।

নীচের কোড স্নিপেটে, আমরা স্যান্ডবক্স করার জন্য বাইনারিটির পথ এবং একটি execve syscall-এ যে আর্গুমেন্টগুলি পাস করতে হবে তা সংজ্ঞায়িত করি। আপনি executor.h হেডার ফাইলে দেখতে পাচ্ছেন, আমরা envp এর জন্য একটি মান নির্দিষ্ট করি না এবং তাই প্যারেন্ট প্রক্রিয়া থেকে পরিবেশটি অনুলিপি করি। মনে রাখবেন, প্রথম আর্গুমেন্ট সর্বদা কার্যকর করা প্রোগ্রামের নাম, এবং আমাদের স্নিপেট অন্য কোন আর্গুমেন্টকে সংজ্ঞায়িত করে না।

এই নির্বাহক পদ্ধতির উদাহরণ হল: স্ট্যাটিক এবং টুল

#include "sandboxed_api/sandbox2/executor.h"

std::string path = "path/to/binary";
std::vector<std::string> args = {path};  // args[0] will become the sandboxed
                                         // process' argv[0], typically the
                                         // path to the binary.
auto executor = absl::make_unique<sandbox2::Executor>(path, args);

পদ্ধতি 2: Sandbox2 Forkserver - নির্বাহককে বলুন কখন স্যান্ডবক্স করা হবে

এই পদ্ধতিটি শুরু করার সময় আনস্যান্ডবক্সড হওয়ার নমনীয়তা প্রদান করে এবং তারপরে ::sandbox2::Client::SandboxMeHere() কল করে স্যান্ডবক্সিংয়ে কখন প্রবেশ করতে হবে তা বেছে নেওয়া হয়। আপনি যখন স্যান্ডবক্সিং শুরু করতে চান তখন এটি আপনাকে কোডে সংজ্ঞায়িত করতে সক্ষম হতে হবে এবং এটি একক-থ্রেডেড হতে হবে (কেন FAQ এ পড়ুন)।

নীচের কোড স্নিপেটে, আমরা উপরের পদ্ধতি 1 এ বর্ণিত একই কোড ব্যবহার করি। যাইহোক, প্রারম্ভের সময় একটি আনস্যান্ডবক্সড পদ্ধতিতে প্রোগ্রাম চালানোর অনুমতি দেওয়ার জন্য, আমরা set_enable_sandbox_before_exec(false) বলি।

#include "sandboxed_api/sandbox2/executor.h"

std::string path = "path/to/binary";
std::vector<std::string> args = {path};
auto executor = absl::make_unique<sandbox2::Executor>(path, args);
executor->set_enable_sandbox_before_exec(false);

যেহেতু নির্বাহকের কাছে এখন একটি নিষ্ক্রিয় স্যান্ডবক্স রয়েছে যতক্ষণ না এটি স্যান্ডবক্সী দ্বারা অবহিত করা হয়, আমাদের একটি ::sandbox2::Client উদাহরণ তৈরি করতে হবে, নির্বাহক এবং স্যান্ডবক্সীর মধ্যে যোগাযোগ স্থাপন করতে হবে এবং তারপর নির্বাহককে অবহিত করতে হবে যে আমাদের আরম্ভ করা শেষ হয়েছে এবং আমরা sandbox2_client.SandboxMeHere() কল করে এখন স্যান্ডবক্সিং শুরু করতে চান।

// main() of sandboxee
int main(int argc, char** argv) {
  gflags::ParseCommandLineFlags(&argc, &argv, false);

  // Set-up the sandbox2::Client object, using a file descriptor (1023).
  sandbox2::Comms comms(sandbox2::Comms::kSandbox2ClientCommsFD);
  sandbox2::Client sandbox2_client(&comms);
  // Enable sandboxing from here.
  sandbox2_client.SandboxMeHere();
  …

এই নির্বাহক পদ্ধতির একটি উদাহরণ হল crc4 , যেখানে crc4bin.cc হল স্যান্ডবক্সী এবং নির্বাহককে ( crc4sandbox.cc ) যখন এটি স্যান্ডবক্সে প্রবেশ করবে তখন তাকে অবহিত করে।

পদ্ধতি 3: কাস্টম ফর্কসার্ভার - একটি বাইনারি প্রস্তুত করুন, কাঁটাচামচের অনুরোধের জন্য অপেক্ষা করুন এবং নিজের হাতে স্যান্ডবক্স করুন

এই মোডটি আপনাকে একটি বাইনারি শুরু করতে, এটিকে স্যান্ডবক্সিংয়ের জন্য প্রস্তুত করতে এবং, আপনার বাইনারির জীবনচক্রের একটি নির্দিষ্ট মুহুর্তে, এটি নির্বাহকের কাছে উপলব্ধ করতে দেয়।

নির্বাহক আপনার বাইনারিতে একটি ফর্ক অনুরোধ পাঠাবে, যা fork() (এর মাধ্যমে ::sandbox2::ForkingClient::WaitAndFork() ) করবে। নতুন তৈরি প্রক্রিয়াটি ::sandbox2::Client::SandboxMeHere() দিয়ে স্যান্ডবক্স করার জন্য প্রস্তুত হবে।

#include "sandboxed_api/sandbox2/executor.h"

// Start the custom ForkServer
std::string path = "path/to/binary";
std::vector<std::string> args = {path};
auto fork_executor = absl::make_unique<sandbox2::Executor>(path, args);
fork_executor->StartForkServer();

// Initialize Executor with Comms channel to the ForkServer
auto executor = absl::make_unique<sandbox2::Executor>(
    fork_executor->ipc()->GetComms());

মনে রাখবেন যে এই মোডটি বেশ জটিল এবং শুধুমাত্র কয়েকটি নির্দিষ্ট ক্ষেত্রে প্রযোজ্য; উদাহরণস্বরূপ, যখন আপনার মেমরির প্রয়োজনীয়তা শক্ত থাকে। আপনি COW থেকে উপকৃত হবেন কিন্তু খারাপ দিক আছে যে কোন প্রকৃত ASLR নেই। আরেকটি সাধারণ ব্যবহারের উদাহরণ হতে পারে যখন স্যান্ডবক্সির একটি দীর্ঘ, CPU- নিবিড় ইনিশিয়ালাইজেশন থাকে যা অবিশ্বস্ত ডেটা প্রক্রিয়া করার আগে চালানো যেতে পারে।

এই নির্বাহক পদ্ধতির একটি উদাহরণের জন্য, custom_fork দেখুন।

2. একটি স্যান্ডবক্স নীতি তৈরি করুন৷

একবার আপনার একজন নির্বাহক হয়ে গেলে, আপনি সম্ভবত স্যান্ডবক্সীর জন্য একটি স্যান্ডবক্স নীতি নির্ধারণ করতে চাইবেন। অন্যথায়, স্যান্ডবক্সী শুধুমাত্র ডিফল্ট Syscall নীতি দ্বারা সুরক্ষিত।

স্যান্ডবক্স নীতির সাথে, উদ্দেশ্য হল স্যান্ডবক্সী যে ফাইলগুলি অ্যাক্সেস করতে পারে সেগুলি এবং সেইসাথে যে ফাইলগুলি অ্যাক্সেস করতে পারে সেগুলিকে সীমাবদ্ধ করা৷ আপনি যে কোডটি স্যান্ডবক্স করার পরিকল্পনা করছেন তার দ্বারা প্রয়োজনীয় syscalls সম্পর্কে আপনার বিস্তারিত ধারণা থাকতে হবে। সিস্কালগুলি পর্যবেক্ষণ করার একটি উপায় হল লিনাক্সের কমান্ড-লাইন টুল স্ট্রেস দিয়ে কোড চালানো।

একবার আপনার কাছে syscalls এর তালিকা হয়ে গেলে, আপনি নীতি নির্ধারণ করতে PolicyBuilder ব্যবহার করতে পারেন। PolicyBuilder অনেক সুবিধা এবং সহায়ক ফাংশন নিয়ে আসে যা অনেক সাধারণ ক্রিয়াকলাপের অনুমতি দেয়। নিম্নলিখিত তালিকা শুধুমাত্র উপলব্ধ ফাংশন একটি ছোট উদ্ধৃতি:

  • প্রসেস স্টার্টআপের জন্য যেকোনও সিস্ক্যালের অনুমতি দিন:
    • AllowStaticStartup();
    • AllowDynamicStartup();
  • যেকোন খোলা /পড়া /লেখা* সিস্কালগুলিকে অনুমতি দিন:
    • AllowOpen();
    • AllowRead();
    • AllowWrite();
  • যেকোন প্রস্থান/অ্যাক্সেস/স্টেট সম্পর্কিত সিস্ক্যালগুলিকে অনুমতি দিন:
    • AllowExit();
    • AllowStat();
    • AllowAccess();
  • যেকোন ঘুম/সময় সম্পর্কিত সিস্ক্যালের অনুমতি দিন:
    • AllowTime();
    • AllowSleep();

এই সুবিধার ফাংশন অনুমোদিত যে কোনো প্রাসঙ্গিক syscall তালিকা. এটির সুবিধা রয়েছে যে একই নীতিটি বিভিন্ন আর্কিটেকচারে ব্যবহার করা যেতে পারে যেখানে নির্দিষ্ট syscalls উপলব্ধ নেই (যেমন ARM64-এর কোন ওপেন syscall নেই), তবে প্রয়োজনের তুলনায় আরো বেশি sycsalls সক্ষম করার সামান্য নিরাপত্তা ঝুঁকি সহ। উদাহরণ স্বরূপ, AllowOpen() স্যান্ডবক্সীকে যেকোন খোলা সম্পর্কিত syscall কল করতে সক্ষম করে। আপনি যদি শুধুমাত্র একটি নির্দিষ্ট সিস্ক্যালকে অনুমোদন করতে চান তবে আপনি AllowSyscall(); একসাথে একাধিক syscalls অনুমতি দিতে আপনি AllowSyscalls() ব্যবহার করতে পারেন।

এখনও পর্যন্ত নীতি শুধুমাত্র syscall শনাক্তকারী চেক করে। আপনার যদি নীতিটিকে আরও শক্তিশালী করার প্রয়োজন হয় এবং আপনি এমন একটি নীতি সংজ্ঞায়িত করতে চান যেখানে আপনি শুধুমাত্র নির্দিষ্ট আর্গুমেন্ট সহ একটি syscall অনুমোদন করেন, তাহলে আপনাকে AddPolicyOnSyscall() বা AddPolicyOnSyscalls() ব্যবহার করতে হবে। এই ফাংশনগুলি শুধুমাত্র একটি আর্গুমেন্ট হিসাবে syscall ID গ্রহণ করে না, কিন্তু Linux কার্নেল থেকে bpf হেল্পার ম্যাক্রো ব্যবহার করে একটি কাঁচা seccomp-bpf ফিল্টারও নেয়। BPF সম্পর্কে আরও তথ্যের জন্য কার্নেল ডকুমেন্টেশন দেখুন। আপনি যদি নিজেকে পুনরাবৃত্তিমূলক BPF কোড লিখতে দেখেন যা আপনি মনে করেন একটি ব্যবহারযোগ্যতা-র্যাপার থাকা উচিত, তাহলে নির্দ্বিধায় একটি বৈশিষ্ট্য অনুরোধ ফাইল করুন।

syscall-সম্পর্কিত ফাংশন ছাড়াও, PolicyBuilder স্যান্ডবক্সে ফাইল/ডিরেক্টরি আবদ্ধ করার জন্য AddFile() বা AddDirectory() এর মতো ফাইল-সিস্টেম-সম্পর্কিত অনেকগুলি ফাংশনও প্রদান করে। AddTmpfs() সহায়ক স্যান্ডবক্সের মধ্যে একটি অস্থায়ী ফাইল স্টোরেজ যোগ করতে ব্যবহার করা যেতে পারে।

একটি বিশেষভাবে দরকারী ফাংশন হল AddLibrariesForBinary() যা একটি বাইনারি দ্বারা প্রয়োজনীয় লাইব্রেরি এবং লিঙ্কার যোগ করে।

syscalls to allowlist এ আসা দুর্ভাগ্যবশত এখনও কিছুটা ম্যানুয়াল কাজ। আপনার বাইনারি চাহিদাগুলি জানেন এমন syscalls দিয়ে একটি নীতি তৈরি করুন এবং একটি সাধারণ কাজের চাপ দিয়ে এটি চালান। যদি লঙ্ঘন শুরু হয়, তাহলে syscall-এর অনুমতি দিন এবং প্রক্রিয়াটি পুনরাবৃত্তি করুন। আপনি যদি এমন একটি লঙ্ঘন করেন যা আপনার মতে অনুমোদিত তালিকার জন্য ঝুঁকিপূর্ণ হতে পারে এবং প্রোগ্রামটি সুন্দরভাবে ত্রুটিগুলি পরিচালনা করে, আপনি BlockSyscallWithErrno() এর পরিবর্তে এটিকে একটি ত্রুটি ফিরিয়ে দেওয়ার চেষ্টা করতে পারেন।

#include "sandboxed_api/sandbox2/policy.h"
#include "sandboxed_api/sandbox2/policybuilder.h"
#include "sandboxed_api/sandbox2/util/bpf_helper.h"

std::unique_ptr<sandbox2::Policy> CreatePolicy() {
  return sandbox2::PolicyBuilder()
    .AllowSyscall(__NR_read)  // See also AllowRead()
    .AllowTime()              // Allow time, gettimeofday and clock_gettime
    .AddPolicyOnSyscall(__NR_write, {
        ARG(0),        // fd is the first argument of write (argument #0)
        JEQ(1, ALLOW), // allow write only on fd 1
        KILL,          // kill if not fd 1
    })
    .AddPolicyOnSyscall(__NR_mprotect, {
        ARG_32(2), // prot is a 32-bit wide argument, so it's OK to use *_32
                   // macro here
        JNE32(PROT_READ | PROT_WRITE, KILL), // prot must be the RW, otherwise
                                             // kill the process
        ARG(1), // len is a 64-bit argument
        JNE(0x1000, KILL),  // Allow single page syscalls only, otherwise kill
                            // the process
        ALLOW,              // Allow for the syscall to proceed, if prot and
                            // size match
    })
    // Allow the openat() syscall but always return "not found".
    .BlockSyscallWithErrno(__NR_openat, ENOENT)
    .BuildOrDie();
}

3. সীমা সামঞ্জস্য করুন

স্যান্ডবক্স নীতি স্যান্ডবক্সীকে নির্দিষ্ট সিস্ক্যাল কল করতে বাধা দেয় এবং এইভাবে আক্রমণের পৃষ্ঠকে হ্রাস করে। যাইহোক, একজন আক্রমণকারী অনির্দিষ্টকালের জন্য একটি প্রক্রিয়া চালিয়ে বা RAM এবং অন্যান্য সংস্থানগুলিকে ক্লান্ত করে অবাঞ্ছিত প্রভাব সৃষ্টি করতে সক্ষম হতে পারে।

এই হুমকি মোকাবেলা করার জন্য, স্যান্ডবক্সী ডিফল্টরূপে কঠোর কার্যকরী সীমার অধীনে চলে। যদি এই ডিফল্ট সীমাগুলি আপনার প্রোগ্রামের বৈধ নির্বাহের জন্য সমস্যার সৃষ্টি করে, আপনি sandbox2::Limits নির্বাহক বস্তুতে limits() কল করে সীমাবদ্ধ ক্লাস ব্যবহার করে সেগুলি সামঞ্জস্য করতে পারেন।

নীচের কোড স্নিপেট কিছু উদাহরণ সীমা সমন্বয় দেখায়। সমস্ত উপলব্ধ বিকল্প limits.h হেডার ফাইলে নথিভুক্ত করা হয়েছে।

// Restrict the address space size of the sandboxee to 4 GiB.
executor->limits()->set_rlimit_as(4ULL << 30);
// Kill sandboxee with SIGXFSZ if it writes more than 1 GiB to the filesystem.
executor->limits()->set_rlimit_fsize(1ULL << 30);
// Number of file descriptors which can be used by the sandboxee.
executor->limits()->set_rlimit_nofile(1ULL << 10);
// The sandboxee is not allowed to create core files.
executor->limits()->set_rlimit_core(0);
// Maximum 300s of real CPU time.
executor->limits()->set_rlimit_cpu(300);
// Maximum 120s of wall time.
executor->limits()->set_walltime_limit(absl::Seconds(120));

sandbox2::Limits শ্রেণী ব্যবহারের উদাহরণের জন্য, উদাহরণ টুলটি দেখুন।

4. স্যান্ডবক্স চালান

পূর্ববর্তী বিভাগগুলিতে, আপনি স্যান্ডবক্সযুক্ত পরিবেশ, নীতি, এবং নির্বাহক এবং স্যান্ডবক্সী প্রস্তুত করেছেন। পরবর্তী ধাপ হল Sandbox2 অবজেক্ট তৈরি করা এবং এটি চালানো।

সিঙ্ক্রোনাসভাবে চালান

স্যান্ডবক্স সিঙ্ক্রোনাসভাবে চলতে পারে, ফল না পাওয়া পর্যন্ত ব্লক করা যায়। নীচের কোড স্নিপেট Sandbox2 অবজেক্টের ইনস্ট্যান্টেশন এবং এর সিঙ্ক্রোনাস এক্সিকিউশন প্রদর্শন করে। আরো বিস্তারিত উদাহরণের জন্য, স্ট্যাটিক দেখুন।

#include "sandboxed_api/sandbox2/sandbox2.h"

sandbox2::Sandbox2 s2(std::move(executor), std::move(policy));
sandbox2::Result result = s2.Run();  // Synchronous
LOG(INFO) << "Result of sandbox execution: " << result.ToString();

অ্যাসিঙ্ক্রোনাসভাবে চালান

আপনি স্যান্ডবক্সটি অ্যাসিঙ্ক্রোনাসভাবে চালাতে পারেন, এইভাবে ফলাফল না পাওয়া পর্যন্ত ব্লক করা যাবে না। উদাহরণস্বরূপ, স্যান্ডবক্সির সাথে যোগাযোগ করার সময় এটি দরকারী। নীচের কোড স্নিপেট এই ব্যবহারের ক্ষেত্রে প্রদর্শন করে, আরও বিস্তারিত উদাহরণের জন্য crc4 এবং টুল দেখুন।

#include "sandboxed_api/sandbox2/sandbox2.h"

sandbox2::Sandbox2 s2(std::move(executor), std::move(policy));
if (s2.RunAsync()) {
  // Communicate with sandboxee, use s2.Kill() to kill it if needed
  // ...
}
Sandbox2::Result result = s2.AwaitResult();
LOG(INFO) << "Final execution status: " << result.ToString();

5. স্যান্ডবক্সীর সাথে যোগাযোগ করা

ডিফল্টরূপে, নির্বাহক ফাইল বর্ণনাকারীর মাধ্যমে স্যান্ডবক্সীর সাথে যোগাযোগ করতে পারে। এটি আপনার প্রয়োজন হতে পারে, উদাহরণস্বরূপ যদি আপনি স্যান্ডবক্সীর সাথে একটি ফাইল ভাগ করতে চান বা স্যান্ডবক্সীর স্ট্যান্ডার্ড আউটপুট পড়তে চান৷

যাইহোক, আপনার সম্ভবত নির্বাহক এবং স্যান্ডবক্সির মধ্যে আরও জটিল যোগাযোগের যুক্তির প্রয়োজন রয়েছে। comms API ( comms.h হেডার ফাইলটি দেখুন) পূর্ণসংখ্যা, স্ট্রিং, বাইট বাফার, প্রোটোবাফ বা ফাইল বর্ণনাকারী পাঠাতে ব্যবহার করা যেতে পারে।

শেয়ারিং ফাইল বর্ণনাকারী

ইন্টার-প্রসেস কমিউনিকেশন API ব্যবহার করে ( ipc.h দেখুন), আপনি MapFd() বা ReceiveFd() ব্যবহার করতে পারেন :

  • এক্সিকিউটর থেকে স্যান্ডবক্সীতে ফাইল বর্ণনাকারীদের ম্যাপ করতে MapFd() ব্যবহার করুন। এটি স্যান্ডবক্সে ব্যবহারের জন্য নির্বাহক থেকে খোলা একটি ফাইল ভাগ করতে ব্যবহার করা যেতে পারে। একটি উদাহরণ ব্যবহার স্ট্যাটিক দেখা যেতে পারে।

    // The executor opened /proc/version and passes it to the sandboxee as stdin
    executor->ipc()->MapFd(proc_version_fd, STDIN_FILENO);
    
  • একটি সকেটপেয়ার এন্ডপয়েন্ট তৈরি করতে ReceiveFd() ব্যবহার করুন। এটি স্যান্ডবক্সীর স্ট্যান্ডার্ড আউটপুট বা স্ট্যান্ডার্ড ত্রুটিগুলি পড়তে ব্যবহার করা যেতে পারে। একটি উদাহরণ ব্যবহার টুল দেখা যেতে পারে.

    // The executor receives a file descriptor of the sandboxee stdout
    int recv_fd1 = executor->ipc())->ReceiveFd(STDOUT_FILENO);
    

comms API ব্যবহার করে

Sandbox2 একটি সুবিধাজনক comms API প্রদান করে। এটি নির্বাহক এবং স্যান্ডবক্সীর মধ্যে পূর্ণসংখ্যা, স্ট্রিং বা বাইট বাফারগুলি ভাগ করার একটি সহজ এবং সহজ উপায়৷ নীচে কিছু কোড স্নিপেট রয়েছে যা আপনি crc4 উদাহরণে খুঁজে পেতে পারেন।

comms API দিয়ে শুরু করার জন্য, আপনাকে প্রথমে Sandbox2 অবজেক্ট থেকে comms অবজেক্টটি পেতে হবে:

sandbox2::Comms* comms = s2.comms();

একবার comms অবজেক্ট উপলব্ধ হলে, Send* ফ্যামিলি অফ ফাংশনগুলির একটি ব্যবহার করে ডেটা স্যান্ডবক্সীতে পাঠানো যেতে পারে। আপনি crc4 উদাহরণে comms API-এর একটি উদাহরণ ব্যবহার করতে পারেন। নীচের কোড স্নিপেট সেই উদাহরণ থেকে একটি উদ্ধৃতি দেখায়। নির্বাহক SendBytes(buf, size) সহ একটি unsigned char buf[size] পাঠায়:

if (!(comms->SendBytes(static_cast<const uint8_t*>(buf), sz))) {
  /* handle error */
}

Sandboxee থেকে ডেটা পেতে, Recv* ফাংশনগুলির একটি ব্যবহার করুন। নিচের কোড স্নিপেটটি crc4 উদাহরণ থেকে একটি উদ্ধৃতি। নির্বাহক একটি 32-বিট স্বাক্ষরবিহীন পূর্ণসংখ্যাতে চেকসাম গ্রহণ করে: uint32_t crc4;

if (!(comms->RecvUint32(&crc4))) {
  /* handle error */
}

বাফারের সাথে ডেটা শেয়ার করা

আরেকটি ডেটা শেয়ারিং কার্যকারিতা হল বাফার API ব্যবহার করে প্রচুর পরিমাণে ডেটা শেয়ার করা এবং এক্সিকিউটর এবং স্যান্ডবক্সীর মধ্যে পাঠানো ব্যয়বহুল কপিগুলি এড়ানো।

এক্সিকিউটর একটি বাফার তৈরি করে, হয় আকার এবং ডেটা পাস করার জন্য, অথবা সরাসরি একটি ফাইল বর্ণনাকারী থেকে, এবং স্যান্ডবক্সিতে comms->SendFD() এবং স্যান্ডবক্সিতে comms->RecvFD() ব্যবহার করে এটি স্যান্ডবক্সিতে প্রেরণ করে।

নীচের কোড স্নিপেটে, আপনি নির্বাহকের দিকটি দেখতে পারেন। স্যান্ডবক্সটি অ্যাসিঙ্ক্রোনাসভাবে চলে এবং স্যান্ডবক্সীর সাথে একটি বাফারের মাধ্যমে ডেটা ভাগ করে:

// start the sandbox asynchronously
s2.RunAsync();

// instantiate the comms object
sandbox2::Comms* comms = s2.comms();

// random buffer data we want to send
constexpr unsigned char buffer_data[] = /* random data */;
constexpr unsigned int buffer_dataLen = 34;

// create sandbox2 buffer
absl::StatusOr<std::unique_ptr<sandbox2::Buffer>> buffer =
     sandbox2::Buffer::CreateWithSize(1ULL << 20 /* 1Mib */);
std::unique_ptr<sandbox2::Buffer> buffer_ptr = std::move(buffer).value();

// point to the sandbox2 buffer and fill with data
uint8_t* buf = buffer_ptr‑>data();
memcpy(buf, buffer_data, buffer_data_len);

// send the data to the sandboxee
comms‑>SendFd(buffer_ptr‑>fd());

স্যান্ডবক্সীর পাশে, আপনাকে একটি বাফার অবজেক্টও তৈরি করতে হবে এবং এক্সিকিউটরের পাঠানো ফাইল বর্ণনাকারী থেকে ডেটা পড়তে হবে:

// establish the communication with the executor
int fd;
comms.RecvFD(&fd);

// create the buffer
absl::StatusOr<std::unique_ptr<sandbox2::Buffer>> buffer =
     sandbox2::Buffer::createFromFd(fd);

// get the data
auto buffer_ptr = std::move(buffer).value();
uint8_t* buf = buffer_ptr‑>data();

/* work with the buf object */

6. স্যান্ডবক্স থেকে প্রস্থান করা হচ্ছে

আপনি কীভাবে স্যান্ডবক্স চালান তার উপর নির্ভর করে ( এই ধাপটি দেখুন), আপনাকে স্যান্ডবক্সটি বন্ধ করার উপায় এবং এইভাবে স্যান্ডবক্সীকে সামঞ্জস্য করতে হবে।

সিঙ্ক্রোনাসভাবে চলমান একটি স্যান্ডবক্স থেকে প্রস্থান করা হচ্ছে

যদি স্যান্ডবক্সটি সিঙ্ক্রোনাসভাবে চলছে, তবে স্যান্ডবক্স শেষ হলেই রান ফিরে আসবে। তাই অবসানের জন্য কোন অতিরিক্ত পদক্ষেপের প্রয়োজন নেই। নীচের কোড স্নিপেট এই দৃশ্যকল্প দেখায়:

Sandbox2::Result result = s2.Run();
LOG(INFO) << "Final execution status: " << result.ToString();

অ্যাসিঙ্ক্রোনাসভাবে চলমান একটি স্যান্ডবক্স থেকে প্রস্থান করা হচ্ছে

যদি স্যান্ডবক্সটি অ্যাসিঙ্ক্রোনাসভাবে চলছে, তাহলে সমাপ্তির জন্য দুটি বিকল্প উপলব্ধ। প্রথমত, আপনি শুধু স্যান্ডবক্সের সমাপ্তির জন্য অপেক্ষা করতে পারেন এবং চূড়ান্ত মৃত্যুদন্ডের স্থিতি পেতে পারেন:

sandbox2::Result result = s2.AwaitResult();
LOG(INFO) << "Final execution status: " << result.ToString();

বিকল্পভাবে, আপনি যেকোন সময় স্যান্ডবক্সীকে মেরে ফেলতে পারেন, তবে এখনও AwaitResult() কল করার পরামর্শ দেওয়া হচ্ছে কারণ এর মধ্যে অন্য কারণে স্যান্ডবক্সী বন্ধ হয়ে যেতে পারে:

s2.Kill();
sandbox2::Result result = s2.AwaitResult();
LOG(INFO) << "Final execution status: " << result.ToString();

7. পরীক্ষা

অন্য যেকোনো কোডের মতো, আপনার স্যান্ডবক্স বাস্তবায়নের পরীক্ষা থাকা উচিত। স্যান্ডবক্স পরীক্ষাগুলি প্রোগ্রামের সঠিকতা পরীক্ষা করার জন্য নয়, বরং স্যান্ডবক্স লঙ্ঘনের মতো সমস্যা ছাড়াই স্যান্ডবক্স করা প্রোগ্রাম চালানো যাবে কিনা তা পরীক্ষা করার জন্য। এটি স্যান্ডবক্স নীতি সঠিক কিনা তাও নিশ্চিত করে।

একটি স্যান্ডবক্সড প্রোগ্রাম একইভাবে পরীক্ষা করা হয় যেভাবে আপনি এটিকে উত্পাদনে চালান, আর্গুমেন্ট এবং ইনপুট ফাইলগুলির সাথে এটি সাধারণত প্রক্রিয়া করে।

এই পরীক্ষাগুলি একটি শেল পরীক্ষা বা সাব প্রক্রিয়া ব্যবহার করে C++ পরীক্ষার মতো সহজ হতে পারে। অনুপ্রেরণা জন্য উদাহরণ দেখুন.

উপসংহার

এই পর্যন্ত পড়ার জন্য ধন্যবাদ, আমরা আশা করি আপনি আমাদের গাইড পছন্দ করেছেন এবং এখন আপনার ব্যবহারকারীদের সুরক্ষিত রাখতে সাহায্য করার জন্য আপনার নিজস্ব স্যান্ডবক্স তৈরি করার ক্ষমতা বোধ করছেন।

স্যান্ডবক্স এবং নীতিগুলি তৈরি করা একটি কঠিন কাজ এবং সূক্ষ্ম ত্রুটির প্রবণতা৷ নিরাপদে থাকার জন্য, আমরা আপনাকে একজন নিরাপত্তা বিশেষজ্ঞকে আপনার নীতি এবং কোড পর্যালোচনা করার পরামর্শ দিই।

,

এই পৃষ্ঠায়, আপনি শিখবেন কিভাবে স্যান্ডবক্স 2 দিয়ে আপনার নিজস্ব স্যান্ডবক্সড পরিবেশ তৈরি করবেন। আপনি শিখবেন কিভাবে একটি স্যান্ডবক্স নীতি সংজ্ঞায়িত করতে হয়, এবং কিছু উন্নত, কিন্তু সাধারণ, টুইক। হেডার ফাইলে উদাহরণ এবং কোড ডকুমেন্টেশনের পাশাপাশি গাইড হিসেবে এখানে তথ্য ব্যবহার করুন।

1. একটি স্যান্ডবক্স এক্সিকিউটর পদ্ধতি বেছে নিন

স্যান্ডবক্সিং একজন নির্বাহক দিয়ে শুরু হয় ( স্যান্ডবক্স এক্সিকিউটর দেখুন), যেটি স্যান্ডবক্সী চালানোর জন্য দায়ী। executor.h হেডার ফাইলে এই উদ্দেশ্যে প্রয়োজনীয় API রয়েছে। এপিআই খুবই নমনীয় এবং আপনার ব্যবহারের ক্ষেত্রে কোনটি সবচেয়ে ভালো কাজ করে তা বেছে নিতে দেয়। নিম্নলিখিত বিভাগগুলি 3টি ভিন্ন পদ্ধতি বর্ণনা করে যা থেকে আপনি চয়ন করতে পারেন৷

পদ্ধতি 1: একাকী - স্যান্ডবক্সিং ইতিমধ্যে সক্ষম করে একটি বাইনারি চালান

এটি স্যান্ডবক্সিং ব্যবহার করার সবচেয়ে সহজ উপায় এবং যখন আপনি একটি সম্পূর্ণ বাইনারি স্যান্ডবক্স করতে চান যার জন্য আপনার কাছে কোনও উত্স কোড নেই তখন এটি প্রস্তাবিত পদ্ধতি৷ এটি স্যান্ডবক্সিং ব্যবহার করার সবচেয়ে নিরাপদ উপায়, কারণ সেখানে কোনো আনস্যান্ডবক্সিং ইনিশিয়ালাইজেশন নেই যা বিরূপ প্রভাব ফেলতে পারে।

নীচের কোড স্নিপেটে, আমরা স্যান্ডবক্স করার জন্য বাইনারিটির পথ এবং একটি execve syscall-এ যে আর্গুমেন্টগুলি পাস করতে হবে তা সংজ্ঞায়িত করি। আপনি executor.h হেডার ফাইলে দেখতে পাচ্ছেন, আমরা envp এর জন্য একটি মান নির্দিষ্ট করি না এবং তাই প্যারেন্ট প্রক্রিয়া থেকে পরিবেশটি অনুলিপি করি। মনে রাখবেন, প্রথম আর্গুমেন্ট সর্বদা কার্যকর করা প্রোগ্রামের নাম, এবং আমাদের স্নিপেট অন্য কোন আর্গুমেন্টকে সংজ্ঞায়িত করে না।

এই নির্বাহক পদ্ধতির উদাহরণ হল: স্ট্যাটিক এবং টুল

#include "sandboxed_api/sandbox2/executor.h"

std::string path = "path/to/binary";
std::vector<std::string> args = {path};  // args[0] will become the sandboxed
                                         // process' argv[0], typically the
                                         // path to the binary.
auto executor = absl::make_unique<sandbox2::Executor>(path, args);

পদ্ধতি 2: Sandbox2 Forkserver - নির্বাহককে বলুন কখন স্যান্ডবক্স করা হবে

এই পদ্ধতিটি শুরু করার সময় আনস্যান্ডবক্সড হওয়ার নমনীয়তা প্রদান করে এবং তারপরে ::sandbox2::Client::SandboxMeHere() কল করে স্যান্ডবক্সিংয়ে কখন প্রবেশ করতে হবে তা বেছে নেওয়া হয়। আপনি যখন স্যান্ডবক্সিং শুরু করতে চান তখন এটি আপনাকে কোডে সংজ্ঞায়িত করতে সক্ষম হতে হবে এবং এটি একক-থ্রেডেড হতে হবে (কেন FAQ এ পড়ুন)।

নীচের কোড স্নিপেটে, আমরা উপরের পদ্ধতি 1 এ বর্ণিত একই কোড ব্যবহার করি। যাইহোক, প্রারম্ভের সময় একটি আনস্যান্ডবক্সড পদ্ধতিতে প্রোগ্রাম চালানোর অনুমতি দেওয়ার জন্য, আমরা set_enable_sandbox_before_exec(false) বলি।

#include "sandboxed_api/sandbox2/executor.h"

std::string path = "path/to/binary";
std::vector<std::string> args = {path};
auto executor = absl::make_unique<sandbox2::Executor>(path, args);
executor->set_enable_sandbox_before_exec(false);

যেহেতু নির্বাহকের কাছে এখন একটি নিষ্ক্রিয় স্যান্ডবক্স রয়েছে যতক্ষণ না এটি স্যান্ডবক্সী দ্বারা অবহিত করা হয়, আমাদের একটি ::sandbox2::Client উদাহরণ তৈরি করতে হবে, নির্বাহক এবং স্যান্ডবক্সীর মধ্যে যোগাযোগ স্থাপন করতে হবে এবং তারপর নির্বাহককে অবহিত করতে হবে যে আমাদের আরম্ভ করা শেষ হয়েছে এবং আমরা sandbox2_client.SandboxMeHere() কল করে এখন স্যান্ডবক্সিং শুরু করতে চান।

// main() of sandboxee
int main(int argc, char** argv) {
  gflags::ParseCommandLineFlags(&argc, &argv, false);

  // Set-up the sandbox2::Client object, using a file descriptor (1023).
  sandbox2::Comms comms(sandbox2::Comms::kSandbox2ClientCommsFD);
  sandbox2::Client sandbox2_client(&comms);
  // Enable sandboxing from here.
  sandbox2_client.SandboxMeHere();
  …

এই নির্বাহক পদ্ধতির একটি উদাহরণ হল crc4 , যেখানে crc4bin.cc হল স্যান্ডবক্সী এবং নির্বাহককে ( crc4sandbox.cc ) যখন এটি স্যান্ডবক্সে প্রবেশ করবে তখন তাকে অবহিত করে।

পদ্ধতি 3: কাস্টম ফর্কসার্ভার - একটি বাইনারি প্রস্তুত করুন, কাঁটাচামচের অনুরোধের জন্য অপেক্ষা করুন এবং নিজের হাতে স্যান্ডবক্স করুন

এই মোডটি আপনাকে একটি বাইনারি শুরু করতে, এটিকে স্যান্ডবক্সিংয়ের জন্য প্রস্তুত করতে এবং, আপনার বাইনারির জীবনচক্রের একটি নির্দিষ্ট মুহুর্তে, এটি নির্বাহকের কাছে উপলব্ধ করতে দেয়।

নির্বাহক আপনার বাইনারিতে একটি ফর্ক অনুরোধ পাঠাবে, যা fork() (এর মাধ্যমে ::sandbox2::ForkingClient::WaitAndFork() ) করবে। নতুন তৈরি প্রক্রিয়াটি ::sandbox2::Client::SandboxMeHere() দিয়ে স্যান্ডবক্স করার জন্য প্রস্তুত হবে।

#include "sandboxed_api/sandbox2/executor.h"

// Start the custom ForkServer
std::string path = "path/to/binary";
std::vector<std::string> args = {path};
auto fork_executor = absl::make_unique<sandbox2::Executor>(path, args);
fork_executor->StartForkServer();

// Initialize Executor with Comms channel to the ForkServer
auto executor = absl::make_unique<sandbox2::Executor>(
    fork_executor->ipc()->GetComms());

মনে রাখবেন যে এই মোডটি বেশ জটিল এবং শুধুমাত্র কয়েকটি নির্দিষ্ট ক্ষেত্রে প্রযোজ্য; উদাহরণস্বরূপ, যখন আপনার মেমরির প্রয়োজনীয়তা শক্ত থাকে। আপনি COW থেকে উপকৃত হবেন কিন্তু খারাপ দিক আছে যে কোন প্রকৃত ASLR নেই। আরেকটি সাধারণ ব্যবহারের উদাহরণ হতে পারে যখন স্যান্ডবক্সির একটি দীর্ঘ, CPU- নিবিড় ইনিশিয়ালাইজেশন থাকে যা অবিশ্বস্ত ডেটা প্রক্রিয়া করার আগে চালানো যেতে পারে।

এই নির্বাহক পদ্ধতির একটি উদাহরণের জন্য, custom_fork দেখুন।

2. একটি স্যান্ডবক্স নীতি তৈরি করুন৷

একবার আপনার একজন নির্বাহক হয়ে গেলে, আপনি সম্ভবত স্যান্ডবক্সীর জন্য একটি স্যান্ডবক্স নীতি নির্ধারণ করতে চাইবেন। অন্যথায়, স্যান্ডবক্সী শুধুমাত্র ডিফল্ট Syscall নীতি দ্বারা সুরক্ষিত।

স্যান্ডবক্স নীতির সাথে, উদ্দেশ্য হল স্যান্ডবক্সী যে ফাইলগুলি অ্যাক্সেস করতে পারে সেগুলি এবং সেইসাথে যে ফাইলগুলি অ্যাক্সেস করতে পারে সেগুলিকে সীমাবদ্ধ করা৷ আপনি যে কোডটি স্যান্ডবক্স করার পরিকল্পনা করছেন তার দ্বারা প্রয়োজনীয় syscalls সম্পর্কে আপনার বিস্তারিত ধারণা থাকতে হবে। সিস্কালগুলি পর্যবেক্ষণ করার একটি উপায় হল লিনাক্সের কমান্ড-লাইন টুল স্ট্রেস দিয়ে কোড চালানো।

একবার আপনার কাছে syscalls এর তালিকা হয়ে গেলে, আপনি নীতি নির্ধারণ করতে PolicyBuilder ব্যবহার করতে পারেন। PolicyBuilder অনেক সুবিধা এবং সহায়ক ফাংশন নিয়ে আসে যা অনেক সাধারণ ক্রিয়াকলাপের অনুমতি দেয়। নিম্নলিখিত তালিকা শুধুমাত্র উপলব্ধ ফাংশন একটি ছোট উদ্ধৃতি:

  • প্রসেস স্টার্টআপের জন্য যেকোনও সিস্ক্যালের অনুমতি দিন:
    • AllowStaticStartup();
    • AllowDynamicStartup();
  • যেকোন খোলা /পড়া /লেখা* সিস্কালগুলিকে অনুমতি দিন:
    • AllowOpen();
    • AllowRead();
    • AllowWrite();
  • যেকোন প্রস্থান/অ্যাক্সেস/স্টেট সম্পর্কিত সিস্ক্যালগুলিকে অনুমতি দিন:
    • AllowExit();
    • AllowStat();
    • AllowAccess();
  • যেকোন ঘুম/সময় সম্পর্কিত সিস্ক্যালের অনুমতি দিন:
    • AllowTime();
    • AllowSleep();

এই সুবিধার ফাংশন অনুমোদিত যে কোনো প্রাসঙ্গিক syscall তালিকা. এটির সুবিধা রয়েছে যে একই নীতিটি বিভিন্ন আর্কিটেকচারে ব্যবহার করা যেতে পারে যেখানে নির্দিষ্ট syscalls উপলব্ধ নেই (যেমন ARM64-এর কোন ওপেন syscall নেই), তবে প্রয়োজনের তুলনায় আরো বেশি sycsalls সক্ষম করার সামান্য নিরাপত্তা ঝুঁকি সহ। উদাহরণ স্বরূপ, AllowOpen() স্যান্ডবক্সীকে যেকোন খোলা সম্পর্কিত syscall কল করতে সক্ষম করে। আপনি যদি শুধুমাত্র একটি নির্দিষ্ট সিস্ক্যালকে অনুমোদন করতে চান তবে আপনি AllowSyscall(); একসাথে একাধিক syscalls অনুমতি দিতে আপনি AllowSyscalls() ব্যবহার করতে পারেন।

এখনও পর্যন্ত নীতি শুধুমাত্র syscall শনাক্তকারী চেক করে। আপনার যদি নীতিটিকে আরও শক্তিশালী করার প্রয়োজন হয় এবং আপনি এমন একটি নীতি সংজ্ঞায়িত করতে চান যেখানে আপনি শুধুমাত্র নির্দিষ্ট আর্গুমেন্ট সহ একটি syscall অনুমোদন করেন, তাহলে আপনাকে AddPolicyOnSyscall() বা AddPolicyOnSyscalls() ব্যবহার করতে হবে। এই ফাংশনগুলি শুধুমাত্র একটি আর্গুমেন্ট হিসাবে syscall ID গ্রহণ করে না, কিন্তু Linux কার্নেল থেকে bpf হেল্পার ম্যাক্রো ব্যবহার করে একটি কাঁচা seccomp-bpf ফিল্টারও নেয়। BPF সম্পর্কে আরও তথ্যের জন্য কার্নেল ডকুমেন্টেশন দেখুন। আপনি যদি নিজেকে পুনরাবৃত্তিমূলক BPF কোড লিখতে দেখেন যা আপনি মনে করেন একটি ব্যবহারযোগ্যতা-র্যাপার থাকা উচিত, তাহলে নির্দ্বিধায় একটি বৈশিষ্ট্য অনুরোধ ফাইল করুন।

syscall-সম্পর্কিত ফাংশন ছাড়াও, PolicyBuilder স্যান্ডবক্সে ফাইল/ডিরেক্টরি আবদ্ধ করার জন্য AddFile() বা AddDirectory() এর মতো ফাইল-সিস্টেম-সম্পর্কিত অনেকগুলি ফাংশনও প্রদান করে। AddTmpfs() সহায়ক স্যান্ডবক্সের মধ্যে একটি অস্থায়ী ফাইল স্টোরেজ যোগ করতে ব্যবহার করা যেতে পারে।

একটি বিশেষভাবে দরকারী ফাংশন হল AddLibrariesForBinary() যা একটি বাইনারি দ্বারা প্রয়োজনীয় লাইব্রেরি এবং লিঙ্কার যোগ করে।

syscalls to allowlist এ আসা দুর্ভাগ্যবশত এখনও কিছুটা ম্যানুয়াল কাজ। আপনার বাইনারি চাহিদাগুলি জানেন এমন syscalls দিয়ে একটি নীতি তৈরি করুন এবং একটি সাধারণ কাজের চাপ দিয়ে এটি চালান। যদি লঙ্ঘন শুরু হয়, তাহলে syscall-এর অনুমতি দিন এবং প্রক্রিয়াটি পুনরাবৃত্তি করুন। আপনি যদি এমন একটি লঙ্ঘন করেন যা আপনার মতে অনুমোদিত তালিকার জন্য ঝুঁকিপূর্ণ হতে পারে এবং প্রোগ্রামটি সুন্দরভাবে ত্রুটিগুলি পরিচালনা করে, আপনি BlockSyscallWithErrno() এর পরিবর্তে এটিকে একটি ত্রুটি ফিরিয়ে দেওয়ার চেষ্টা করতে পারেন।

#include "sandboxed_api/sandbox2/policy.h"
#include "sandboxed_api/sandbox2/policybuilder.h"
#include "sandboxed_api/sandbox2/util/bpf_helper.h"

std::unique_ptr<sandbox2::Policy> CreatePolicy() {
  return sandbox2::PolicyBuilder()
    .AllowSyscall(__NR_read)  // See also AllowRead()
    .AllowTime()              // Allow time, gettimeofday and clock_gettime
    .AddPolicyOnSyscall(__NR_write, {
        ARG(0),        // fd is the first argument of write (argument #0)
        JEQ(1, ALLOW), // allow write only on fd 1
        KILL,          // kill if not fd 1
    })
    .AddPolicyOnSyscall(__NR_mprotect, {
        ARG_32(2), // prot is a 32-bit wide argument, so it's OK to use *_32
                   // macro here
        JNE32(PROT_READ | PROT_WRITE, KILL), // prot must be the RW, otherwise
                                             // kill the process
        ARG(1), // len is a 64-bit argument
        JNE(0x1000, KILL),  // Allow single page syscalls only, otherwise kill
                            // the process
        ALLOW,              // Allow for the syscall to proceed, if prot and
                            // size match
    })
    // Allow the openat() syscall but always return "not found".
    .BlockSyscallWithErrno(__NR_openat, ENOENT)
    .BuildOrDie();
}

3. সীমা সামঞ্জস্য করুন

স্যান্ডবক্স নীতি স্যান্ডবক্সীকে নির্দিষ্ট সিস্ক্যাল কল করতে বাধা দেয় এবং এইভাবে আক্রমণের পৃষ্ঠকে হ্রাস করে। যাইহোক, একজন আক্রমণকারী অনির্দিষ্টকালের জন্য একটি প্রক্রিয়া চালিয়ে বা RAM এবং অন্যান্য সংস্থানগুলিকে ক্লান্ত করে অবাঞ্ছিত প্রভাব সৃষ্টি করতে সক্ষম হতে পারে।

এই হুমকি মোকাবেলা করার জন্য, স্যান্ডবক্সী ডিফল্টরূপে কঠোর কার্যকরী সীমার অধীনে চলে। যদি এই ডিফল্ট সীমাগুলি আপনার প্রোগ্রামের বৈধ নির্বাহের জন্য সমস্যার সৃষ্টি করে, আপনি sandbox2::Limits নির্বাহক বস্তুতে limits() কল করে সীমাবদ্ধ ক্লাস ব্যবহার করে সেগুলি সামঞ্জস্য করতে পারেন।

নীচের কোড স্নিপেট কিছু উদাহরণ সীমা সমন্বয় দেখায়। সমস্ত উপলব্ধ বিকল্প limits.h হেডার ফাইলে নথিভুক্ত করা হয়েছে।

// Restrict the address space size of the sandboxee to 4 GiB.
executor->limits()->set_rlimit_as(4ULL << 30);
// Kill sandboxee with SIGXFSZ if it writes more than 1 GiB to the filesystem.
executor->limits()->set_rlimit_fsize(1ULL << 30);
// Number of file descriptors which can be used by the sandboxee.
executor->limits()->set_rlimit_nofile(1ULL << 10);
// The sandboxee is not allowed to create core files.
executor->limits()->set_rlimit_core(0);
// Maximum 300s of real CPU time.
executor->limits()->set_rlimit_cpu(300);
// Maximum 120s of wall time.
executor->limits()->set_walltime_limit(absl::Seconds(120));

sandbox2::Limits শ্রেণী ব্যবহারের উদাহরণের জন্য, উদাহরণ টুলটি দেখুন।

4. স্যান্ডবক্স চালান

পূর্ববর্তী বিভাগগুলিতে, আপনি স্যান্ডবক্সযুক্ত পরিবেশ, নীতি, এবং নির্বাহক এবং স্যান্ডবক্সী প্রস্তুত করেছেন। পরবর্তী ধাপ হল Sandbox2 অবজেক্ট তৈরি করা এবং এটি চালানো।

সিঙ্ক্রোনাসভাবে চালান

স্যান্ডবক্স সিঙ্ক্রোনাসভাবে চলতে পারে, ফল না পাওয়া পর্যন্ত ব্লক করা যায়। নীচের কোড স্নিপেট Sandbox2 অবজেক্টের ইনস্ট্যান্টেশন এবং এর সিঙ্ক্রোনাস এক্সিকিউশন প্রদর্শন করে। আরো বিস্তারিত উদাহরণের জন্য, স্ট্যাটিক দেখুন।

#include "sandboxed_api/sandbox2/sandbox2.h"

sandbox2::Sandbox2 s2(std::move(executor), std::move(policy));
sandbox2::Result result = s2.Run();  // Synchronous
LOG(INFO) << "Result of sandbox execution: " << result.ToString();

অ্যাসিঙ্ক্রোনাসভাবে চালান

আপনি স্যান্ডবক্সটি অ্যাসিঙ্ক্রোনাসভাবে চালাতে পারেন, এইভাবে ফলাফল না পাওয়া পর্যন্ত ব্লক করা যাবে না। উদাহরণস্বরূপ, স্যান্ডবক্সির সাথে যোগাযোগ করার সময় এটি দরকারী। নীচের কোড স্নিপেট এই ব্যবহারের ক্ষেত্রে প্রদর্শন করে, আরও বিস্তারিত উদাহরণের জন্য crc4 এবং টুল দেখুন।

#include "sandboxed_api/sandbox2/sandbox2.h"

sandbox2::Sandbox2 s2(std::move(executor), std::move(policy));
if (s2.RunAsync()) {
  // Communicate with sandboxee, use s2.Kill() to kill it if needed
  // ...
}
Sandbox2::Result result = s2.AwaitResult();
LOG(INFO) << "Final execution status: " << result.ToString();

5. স্যান্ডবক্সীর সাথে যোগাযোগ করা

ডিফল্টরূপে, নির্বাহক ফাইল বর্ণনাকারীর মাধ্যমে স্যান্ডবক্সীর সাথে যোগাযোগ করতে পারে। এটি আপনার প্রয়োজন হতে পারে, উদাহরণস্বরূপ যদি আপনি স্যান্ডবক্সীর সাথে একটি ফাইল ভাগ করতে চান বা স্যান্ডবক্সীর স্ট্যান্ডার্ড আউটপুট পড়তে চান৷

যাইহোক, আপনার সম্ভবত নির্বাহক এবং স্যান্ডবক্সির মধ্যে আরও জটিল যোগাযোগের যুক্তির প্রয়োজন রয়েছে। comms API ( comms.h হেডার ফাইলটি দেখুন) পূর্ণসংখ্যা, স্ট্রিং, বাইট বাফার, প্রোটোবাফ বা ফাইল বর্ণনাকারী পাঠাতে ব্যবহার করা যেতে পারে।

শেয়ারিং ফাইল বর্ণনাকারী

ইন্টার-প্রসেস কমিউনিকেশন API ব্যবহার করে ( ipc.h দেখুন), আপনি MapFd() বা ReceiveFd() ব্যবহার করতে পারেন :

  • এক্সিকিউটর থেকে স্যান্ডবক্সীতে ফাইল বর্ণনাকারীদের ম্যাপ করতে MapFd() ব্যবহার করুন। এটি স্যান্ডবক্সে ব্যবহারের জন্য নির্বাহক থেকে খোলা একটি ফাইল ভাগ করতে ব্যবহার করা যেতে পারে। একটি উদাহরণ ব্যবহার স্ট্যাটিক দেখা যেতে পারে।

    // The executor opened /proc/version and passes it to the sandboxee as stdin
    executor->ipc()->MapFd(proc_version_fd, STDIN_FILENO);
    
  • একটি সকেটপেয়ার এন্ডপয়েন্ট তৈরি করতে ReceiveFd() ব্যবহার করুন। এটি স্যান্ডবক্সীর স্ট্যান্ডার্ড আউটপুট বা স্ট্যান্ডার্ড ত্রুটিগুলি পড়তে ব্যবহার করা যেতে পারে। একটি উদাহরণ ব্যবহার টুল দেখা যেতে পারে.

    // The executor receives a file descriptor of the sandboxee stdout
    int recv_fd1 = executor->ipc())->ReceiveFd(STDOUT_FILENO);
    

comms API ব্যবহার করে

Sandbox2 একটি সুবিধাজনক comms API প্রদান করে। এটি নির্বাহক এবং স্যান্ডবক্সীর মধ্যে পূর্ণসংখ্যা, স্ট্রিং বা বাইট বাফারগুলি ভাগ করার একটি সহজ এবং সহজ উপায়৷ নীচে কিছু কোড স্নিপেট রয়েছে যা আপনি crc4 উদাহরণে খুঁজে পেতে পারেন।

comms API দিয়ে শুরু করার জন্য, আপনাকে প্রথমে Sandbox2 অবজেক্ট থেকে comms অবজেক্টটি পেতে হবে:

sandbox2::Comms* comms = s2.comms();

একবার comms অবজেক্ট উপলব্ধ হলে, Send* ফ্যামিলি অফ ফাংশনগুলির একটি ব্যবহার করে ডেটা স্যান্ডবক্সীতে পাঠানো যেতে পারে। আপনি crc4 উদাহরণে comms API-এর একটি উদাহরণ ব্যবহার করতে পারেন। নীচের কোড স্নিপেট সেই উদাহরণ থেকে একটি উদ্ধৃতি দেখায়। নির্বাহক SendBytes(buf, size) সহ একটি unsigned char buf[size] পাঠায়:

if (!(comms->SendBytes(static_cast<const uint8_t*>(buf), sz))) {
  /* handle error */
}

Sandboxee থেকে ডেটা পেতে, Recv* ফাংশনগুলির একটি ব্যবহার করুন। নিচের কোড স্নিপেটটি crc4 উদাহরণ থেকে একটি উদ্ধৃতি। নির্বাহক একটি 32-বিট স্বাক্ষরবিহীন পূর্ণসংখ্যাতে চেকসাম গ্রহণ করে: uint32_t crc4;

if (!(comms->RecvUint32(&crc4))) {
  /* handle error */
}

বাফারের সাথে ডেটা শেয়ার করা

আরেকটি ডেটা শেয়ারিং কার্যকারিতা হল বাফার API ব্যবহার করে প্রচুর পরিমাণে ডেটা শেয়ার করা এবং এক্সিকিউটর এবং স্যান্ডবক্সীর মধ্যে পাঠানো ব্যয়বহুল কপিগুলি এড়ানো।

এক্সিকিউটর একটি বাফার তৈরি করে, হয় আকার এবং ডেটা পাস করার জন্য, অথবা সরাসরি একটি ফাইল বর্ণনাকারী থেকে, এবং স্যান্ডবক্সিতে comms->SendFD() এবং স্যান্ডবক্সিতে comms->RecvFD() ব্যবহার করে এটি স্যান্ডবক্সিতে প্রেরণ করে।

নীচের কোড স্নিপেটে, আপনি নির্বাহকের দিকটি দেখতে পারেন। স্যান্ডবক্সটি অ্যাসিঙ্ক্রোনাসভাবে চলে এবং স্যান্ডবক্সীর সাথে একটি বাফারের মাধ্যমে ডেটা ভাগ করে:

// start the sandbox asynchronously
s2.RunAsync();

// instantiate the comms object
sandbox2::Comms* comms = s2.comms();

// random buffer data we want to send
constexpr unsigned char buffer_data[] = /* random data */;
constexpr unsigned int buffer_dataLen = 34;

// create sandbox2 buffer
absl::StatusOr<std::unique_ptr<sandbox2::Buffer>> buffer =
     sandbox2::Buffer::CreateWithSize(1ULL << 20 /* 1Mib */);
std::unique_ptr<sandbox2::Buffer> buffer_ptr = std::move(buffer).value();

// point to the sandbox2 buffer and fill with data
uint8_t* buf = buffer_ptr‑>data();
memcpy(buf, buffer_data, buffer_data_len);

// send the data to the sandboxee
comms‑>SendFd(buffer_ptr‑>fd());

স্যান্ডবক্সীর পাশে, আপনাকে একটি বাফার অবজেক্টও তৈরি করতে হবে এবং এক্সিকিউটরের পাঠানো ফাইল বর্ণনাকারী থেকে ডেটা পড়তে হবে:

// establish the communication with the executor
int fd;
comms.RecvFD(&fd);

// create the buffer
absl::StatusOr<std::unique_ptr<sandbox2::Buffer>> buffer =
     sandbox2::Buffer::createFromFd(fd);

// get the data
auto buffer_ptr = std::move(buffer).value();
uint8_t* buf = buffer_ptr‑>data();

/* work with the buf object */

6. স্যান্ডবক্স থেকে প্রস্থান করা হচ্ছে

আপনি কীভাবে স্যান্ডবক্স চালান তার উপর নির্ভর করে ( এই ধাপটি দেখুন), আপনাকে স্যান্ডবক্সটি বন্ধ করার উপায় এবং এইভাবে স্যান্ডবক্সীকে সামঞ্জস্য করতে হবে।

সিঙ্ক্রোনাসভাবে চলমান একটি স্যান্ডবক্স থেকে প্রস্থান করা হচ্ছে

যদি স্যান্ডবক্সটি সিঙ্ক্রোনাসভাবে চলছে, তবে স্যান্ডবক্স শেষ হলেই রান ফিরে আসবে। তাই অবসানের জন্য কোন অতিরিক্ত পদক্ষেপের প্রয়োজন নেই। নীচের কোড স্নিপেট এই দৃশ্যকল্প দেখায়:

Sandbox2::Result result = s2.Run();
LOG(INFO) << "Final execution status: " << result.ToString();

অ্যাসিঙ্ক্রোনাসভাবে চলমান একটি স্যান্ডবক্স থেকে প্রস্থান করা হচ্ছে

যদি স্যান্ডবক্সটি অ্যাসিঙ্ক্রোনাসভাবে চলছে, তাহলে সমাপ্তির জন্য দুটি বিকল্প উপলব্ধ। প্রথমত, আপনি শুধু স্যান্ডবক্সের সমাপ্তির জন্য অপেক্ষা করতে পারেন এবং চূড়ান্ত মৃত্যুদন্ডের স্থিতি পেতে পারেন:

sandbox2::Result result = s2.AwaitResult();
LOG(INFO) << "Final execution status: " << result.ToString();

বিকল্পভাবে, আপনি যেকোন সময় স্যান্ডবক্সীকে মেরে ফেলতে পারেন, তবে এখনও AwaitResult() কল করার পরামর্শ দেওয়া হচ্ছে কারণ এর মধ্যে অন্য কারণে স্যান্ডবক্সী বন্ধ হয়ে যেতে পারে:

s2.Kill();
sandbox2::Result result = s2.AwaitResult();
LOG(INFO) << "Final execution status: " << result.ToString();

7. পরীক্ষা

অন্য যেকোনো কোডের মতো, আপনার স্যান্ডবক্স বাস্তবায়নের পরীক্ষা থাকা উচিত। স্যান্ডবক্স পরীক্ষাগুলি প্রোগ্রামের সঠিকতা পরীক্ষা করার জন্য নয়, বরং স্যান্ডবক্স লঙ্ঘনের মতো সমস্যা ছাড়াই স্যান্ডবক্স করা প্রোগ্রাম চালানো যাবে কিনা তা পরীক্ষা করার জন্য। এটি স্যান্ডবক্স নীতি সঠিক কিনা তাও নিশ্চিত করে।

একটি স্যান্ডবক্সড প্রোগ্রাম একইভাবে পরীক্ষা করা হয় যেভাবে আপনি এটিকে উত্পাদনে চালান, আর্গুমেন্ট এবং ইনপুট ফাইলগুলির সাথে এটি সাধারণত প্রক্রিয়া করে।

এই পরীক্ষাগুলি একটি শেল পরীক্ষা বা সাব প্রক্রিয়া ব্যবহার করে C++ পরীক্ষার মতো সহজ হতে পারে। অনুপ্রেরণা জন্য উদাহরণ দেখুন.

উপসংহার

এই পর্যন্ত পড়ার জন্য ধন্যবাদ, আমরা আশা করি আপনি আমাদের গাইড পছন্দ করেছেন এবং এখন আপনার ব্যবহারকারীদের সুরক্ষিত রাখতে সাহায্য করার জন্য আপনার নিজস্ব স্যান্ডবক্স তৈরি করার ক্ষমতা বোধ করছেন।

স্যান্ডবক্স এবং নীতিগুলি তৈরি করা একটি কঠিন কাজ এবং সূক্ষ্ম ত্রুটির প্রবণতা৷ নিরাপদে থাকার জন্য, আমরা আপনাকে একজন নিরাপত্তা বিশেষজ্ঞকে আপনার নীতি এবং কোড পর্যালোচনা করার পরামর্শ দিই।