Korumalı Alan2'yi Kullanmaya Başlama

Bu sayfada Sandbox2 ile kendi korumalı alanlı ortamınızı nasıl oluşturacağınızı öğreneceksiniz. Korumalı Alan Politikası'nı ve gelişmiş ancak yaygın olarak yapılan bazı ince ayarları nasıl tanımlayacağınızı öğreneceksiniz. Üstbilgi dosyalarındaki örnekler ve kod dokümanlarının yanı sıra buradaki bilgileri kılavuz olarak kullanın.

1. Korumalı Alan Yürütücü Yöntemi Seçin

Korumalı alan oluşturma işlemi, Sandboxee'yi çalıştırmaktan sorumlu bir yürütücüyle (bkz. Korumalı Alan Yürütücü) başlar. executor.h başlık dosyası, bu amaç için gereken API'yi içerir. API çok esnektir ve kullanım alanınıza en uygun olanı seçmenizi sağlar. Aşağıdaki bölümlerde, aralarından seçim yapabileceğiniz 3 farklı yöntem açıklanmaktadır.

1. Yöntem: Bağımsız – Korumalı alan oluşturma zaten etkin olan bir ikili program yürütme

Bu, korumalı alana almanın en basit yoludur ve kaynak kodu bulunmayan bir ikili programın tamamını korumalı alana almak istediğinizde önerilen yöntemdir. Ayrıca, olumsuz etkileri olabilecek korumalı alana alınmayan başlatma olmadığından, korumalı alana alma işleminin en güvenli yoludur.

Aşağıdaki kod snippet'inde, korumalı alana alınacak ikili programın yolunu ve yönetici syscall'a iletmemiz gereken bağımsız değişkenleri tanımlıyoruz. executor.h üstbilgi dosyasında görebileceğiniz gibi, envp için bir değer belirtmeyiz ve bu nedenle ortamı üst işlemden kopyalarız. İlk bağımsız değişkenin her zaman yürütülecek programın adı olduğunu ve snippet'imizin başka hiçbir bağımsız değişkeni tanımlamadığını unutmayın.

Bu yürütme yöntemine örnek olarak statik ve araç verilebilir.

#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. Yöntem: Sandbox2 Çatal sunucusu – Yürütücüye ne zaman korumalı alana alınacağını bildirin

Bu yöntem, başlatma sırasında korumalı alana alınmama ve ardından ::sandbox2::Client::SandboxMeHere() çağrısı yaparak korumalı alana ne zaman girileceğini seçme esnekliği sunar. Korumalı alana almaya başlamak istediğiniz zamanı kodda tanımlayabilmeniz ve tek iş parçacıklı olması gerekir (nedenini SSS bölümünde bulabilirsiniz).

Aşağıdaki kod snippet'inde, yukarıdaki 1. Yöntemde açıklanan kodu kullanırız. Ancak, başlatma sırasında programın korumalı alana alınmadan yürütülebilmesini sağlamak için set_enable_sandbox_before_exec(false) yöntemini çağırırız.

#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);

Yürütücü, Sandboxee tarafından bildirilene kadar devre dışı bırakılmış bir korumalı alana sahip olduğundan bir ::sandbox2::Client örneği oluşturmamız, yürütücü ile Sandboxee arasında iletişim kurmamız ve ardından başlatma işlemimizin tamamlandığını ve şimdi sandbox2_client.SandboxMeHere() çağrısı yaparak korumalı alana başlamak istediğimizi yürütücüye bildirmemiz gerekiyor.

// 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();
  …

Bu yürütücü yöntemine örnek olarak crc4 verilebilir. Burada crc4bin.cc, Sandboxee'dir ve korumalı alana girmesi gerektiğinde yürütücüye (crc4sandbox.cc) bildirimde bulunur.

3. Yöntem: Özel Çatal sunucusu - İkili program hazırlayın, çatal isteklerini bekleyin ve kendi kendinize korumalı alan oluşturun

Bu mod, bir ikili programı başlatmanızı, korumalı alan için hazırlamanızı ve ikili program yaşam döngüsünün belirli bir anında yürütme aracının kullanımına sunmanızı sağlar.

Yürütücü, ikili programınıza bir çatal isteği gönderir. Bu istek, fork() (::sandbox2::ForkingClient::WaitAndFork() üzerinden) üzerinden yapılır. Yeni oluşturulan işlem, ::sandbox2::Client::SandboxMeHere() ile korumalı alana alınmaya hazır olacaktır.

#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());

Bu modun oldukça karmaşık ve yalnızca belirli durumlarda (örneğin, kısıtlı bellek gereksinimleri olduğunda) geçerli olduğunu unutmayın. COW'den faydalanırsınız ancak bunun dezavantajı gerçek bir ASLR olmamasıdır. Diğer bir tipik kullanım örneği, Sandboxee'nin güvenilir olmayan veriler işlenmeden önce çalıştırılabilen uzun ve CPU yoğun bir başlatma işlemine sahip olmasıdır.

Bu yürütücü yönteminin bir örneği için custom_fork konusuna bakın.

2. Korumalı Alan Politikası Oluşturma

Bir yürütücüye sahip olduğunuzda, Sandboxee için bir Korumalı Alan Politikası tanımlamak isteyebilirsiniz. Aksi takdirde, Sandboxee yalnızca Varsayılan Syscall Politikası ile korunur.

Korumalı Alan Politikası'nda amaç, Sandboxee'nin yapabileceği sistem çağrılarını ve bağımsız değişkenlerin yanı sıra erişebileceği dosyaları kısıtlamaktır. Korumalı alana almayı planladığınız kodun gerektirdiği syscall'lar hakkında ayrıntılı bilgi sahibi olmanız gerekir. Sistem çağrılarını gözlemlemenin bir yolu, kodu Linux'un komut satırı araç çubuğuyla çalıştırmaktır.

Siscall'ların listesini edindikten sonra politikayı tanımlamak için PolicyBuilder'ı kullanabilirsiniz. PolicyBuilder, yaygın olarak kullanılan pek çok işleme olanak tanıyan birçok kolaylık ve yardımcı işlev sunar. Aşağıdaki liste kullanılabilir işlevlerin yalnızca küçük bir alıntısıdır:

  • İşlem başlatma için tüm syscall'ları izin verilenler listesine ekleyin:
    • AllowStaticStartup();
    • AllowDynamicStartup();
  • Açık/okuma/yazma* syscall'larını izin verilenler listesine ekleyin:
    • AllowOpen();
    • AllowRead();
    • AllowWrite();
  • Çıkış/erişim/durum ile ilgili tüm syscall'ları izin verilenler listesine ekleyin:
    • AllowExit();
    • AllowStat();
    • AllowAccess();
  • Uyku/zamanla ilgili tüm syscall'ları izin verilenler listesine ekleyin:
    • AllowTime();
    • AllowSleep();

Bu kolaylık işlevleri, ilgili tüm syscall'ları izin verilenler listesine ekler. Bu, aynı politikanın belirli syscall'ların kullanılamadığı farklı mimarilerde kullanılabilmesi avantajına sahiptir (ör. ARM64'te OPEN syscall yoktur) ancak gerekli olabilecekden daha fazla sycsall'ı etkinleştirme gibi küçük bir güvenlik riski taşır. Örneğin, AllowOpen(), Sandboxee'nin açık ve ilgili herhangi bir syscall'ı çağırmasına olanak tanır. Yalnızca belirli bir syscall'ı izin verilenler listesine eklemek istiyorsanız aynı anda birden fazla syscall'a izin vermek için AllowSyscall(); ile AllowSyscalls() kullanabilirsiniz.

Politika şu ana kadar yalnızca sistem çağrısı tanımlayıcısını kontrol eder. Politikayı daha da güçlendirmeniz gerekiyorsa ve yalnızca belirli bağımsız değişkenlerle syscall'a izin verdiğiniz bir politika tanımlamak istiyorsanız AddPolicyOnSyscall() veya AddPolicyOnSyscalls() kullanmanız gerekir. Bu işlevler yalnızca syscall kimliğini bağımsız değişken olarak almakla kalmaz, aynı zamanda Linux çekirdeğindeki bpf yardımcı makrolarını kullanan bir ham seccomp-bpf filtresi de alır. BPF hakkında daha fazla bilgi edinmek için çekirdek dokümanlarına bakın. Kullanılabilirlik sarmalayıcısı olması gerektiğini düşündüğünüz tekrar eden BPF kodu yazdığınızı fark ederseniz özellik isteğinde bulunabilirsiniz.

PolicyBuilder, syscall ile ilgili işlevlerin yanı sıra bir dosyayı/dizini korumalı alana bağlamak için AddFile() veya AddDirectory() gibi dosya sistemiyle ilgili bir dizi işlev de sağlar. AddTmpfs() yardımcısı, korumalı alana geçici bir dosya depolama alanı eklemek için kullanılabilir.

Özellikle kullanışlı bir işlev, ikili programların gerektirdiği kitaplıkları ve bağlayıcıyı ekleyen AddLibrariesForBinary()'dir.

Ne yazık ki izin verilenler listesine ekleme amaçlı sistem çağrısını yapmak, hâlâ biraz manuel bir iş. İkili program ihtiyaçlarınızı bildiğiniz syscall'larla bir politika oluşturun ve bunu ortak bir iş yüküyle çalıştırın. Bir ihlal tetiklenirse sistem çağrısını izin verilenler listesine ekleyin ve işlemi tekrarlayın. İzin verilenler listesine eklenmenin riskli olabileceğini düşündüğünüz bir ihlalle karşılaşırsanız ve program, hataları sorunsuz bir şekilde ele alırsa bunun yerine BlockSyscallWithErrno() kullanarak hata döndürmesini deneyebilirsiniz.

#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. Sınırları Ayarla

Korumalı Alan Politikası, Sandboxee'nin belirli syscall'ları çağırmasını engelleyerek saldırı yüzeyini azaltır. Bununla birlikte, saldırganlar bir işlemi süresiz olarak çalıştırarak veya RAM ile diğer kaynakları tüketerek istenmeyen etkilere neden olabilir.

Sandboxee, bu tehdidi ele almak için varsayılan olarak sıkı yürütme sınırları altında çalışır. Bu varsayılan sınırlar, programınızın geçerli şekilde yürütülmesi konusunda sorunlara neden oluyorsa yürütücü nesnesinde limits() çağrısı yaparak sandbox2::Limits sınıfını kullanarak bunları ayarlayabilirsiniz.

Aşağıdaki kod snippet'inde bazı örnek sınır düzenlemeleri gösterilmektedir. Kullanılabilir tüm seçenekler, limits.h başlık dosyasında belgelenmiştir.

// 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 sınıfının kullanımına ilişkin bir örnek için örnek aracı inceleyin.

4. Korumalı Alan'ı çalıştırın

Önceki bölümlerde korumalı alan, politika, yürütücü ve Sandboxee'yi hazırladınız. Bir sonraki adım, Sandbox2 nesnesini oluşturmak ve çalıştırmaktır.

Eşzamanlı olarak çalıştır

Korumalı alan eşzamanlı olarak çalışabilir. Bu nedenle, sonuç oluşana kadar engellenir. Aşağıdaki kod snippet'i, Sandbox2 nesnesinin örneklenmesini ve eşzamanlı yürütmesini gösterir. Daha ayrıntılı bir örnek için statik bölümüne bakın.

#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();

Eşzamansız olarak çalıştır

Ayrıca, korumalı alanı eşzamansız olarak da çalıştırabilirsiniz. Böylece, sonuç kurulana kadar engelleme yapamazsınız. Bu, örneğin Sandboxee ile iletişim kurarken yararlıdır. Aşağıdaki kod snippet'i bu kullanım alanını gösterir. Daha ayrıntılı örnekler için crc4 ve tool öğesine bakın.

#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. Sandboxee ile iletişim kurma

Varsayılan olarak yürütücü, dosya tanımlayıcıları üzerinden Sandboxee ile iletişim kurabilir. Örneğin, bir dosyayı Korumalı Alan ile paylaşmak veya Korumalı Alan'ın standart çıkışını okumak istiyorsanız bunu yapmanız gerekebilir.

Ancak muhtemelen yürütücü ile Sandboxee'nin arasında daha karmaşık bir iletişim mantığına ihtiyacınız vardır. comms API'si (comms.h başlık dosyasına bakın) tam sayılar, dizeler, bayt arabellekleri, protobuflar veya dosya açıklayıcıları göndermek için kullanılabilir.

Dosya Açıklayıcılarını Paylaşma

Inter-Process Communication API'sini (ipc.h) kullanarak MapFd() veya ReceiveFd() kullanabilirsiniz:

  • Yürütücüdeki dosya tanımlayıcılarını Sandboxee ile eşlemek için MapFd() kullanın. Bu özellik, yürütücüden açılan bir dosyayı Sandboxee'de kullanılmak üzere paylaşmak için kullanılabilir. Statik bir öğede örnek bir kullanım görülebilir.

    // The executor opened /proc/version and passes it to the sandboxee as stdin
    executor->ipc()->MapFd(proc_version_fd, STDIN_FILENO);
    
  • Yuva çifti uç noktası oluşturmak için ReceiveFd() kullanın. Bu değer, Sandboxee'nin standart çıkışını veya standart hatalarını okumak için kullanılabilir. Örnek bir kullanımı araçta görebilirsiniz.

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

İletişim API'sini kullanma

Sandbox2, kullanışlı bir iletişim API'si sağlar. Bu, tam sayıları, dizeleri veya bayt arabelleklerini yürütücü ile Sandboxee arasında paylaşmanın basit ve kolay bir yoludur. Aşağıda crc4 örneğinde bulabileceğiniz bazı kod snippet'leri verilmiştir.

comms API'yi kullanmaya başlamak için ilk olarak Sandbox2 nesnesinden iletişim nesnesini almanız gerekir:

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

İletişim nesnesi kullanılabilir olduğunda veriler, Send* işlev ailesinden biri kullanılarak Sandboxee'ye gönderilebilir. comms API'nin örnek bir kullanımını crc4 örneğinde bulabilirsiniz. Aşağıdaki kod snippet'i bu örnekten bir alıntıyı göstermektedir. Yürütücü, SendBytes(buf, size) ile birlikte bir unsigned char buf[size] gönderir:

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

Sandboxee'den veri almak için Recv* işlevlerinden birini kullanın. Aşağıdaki kod snippet'i crc4 örneğinden bir alıntıdır. Yürütücü, sağlama toplamını 32 bitlik imzasız bir tam sayı olarak alır: uint32_t crc4;

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

Verileri Arabelleklerle Paylaşma

Bir diğer veri paylaşım işlevi de büyük miktarda veri paylaşmak ve yönetici ile Sandboxee arasında gidip gelen pahalı kopyaları önlemek için buffer API'yi kullanmaktır.

Yürütücü, boyuta ve aktarılacak verilere göre veya doğrudan dosya açıklayıcıdan bir Arabellek oluşturur ve yürütücüde comms->SendFD(), Sandboxee'de ise comms->RecvFD() kullanarak bunu Sandboxee'ye iletir.

Aşağıdaki kod snippet'inde yürütme aracının yan tarafını görebilirsiniz. Korumalı alan eşzamansız olarak çalışır ve bir arabellek aracılığıyla Korumalı Alan ile veri paylaşır:

// 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());

Korumalı alan tarafında ayrıca bir arabellek nesnesi oluşturmanız ve yürütücü tarafından gönderilen dosya açıklayıcıdaki verileri okumanız gerekir:

// 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. Korumalı alandan çıkma

Korumalı alanı nasıl çalıştırdığınıza bağlı olarak (bu adıma bakın) korumalı alanı sonlandırma şeklinizi ve ayrıca Korumalı Alan'ı ayarlamanız gerekir.

Eşzamanlı olarak çalışan bir korumalı alandan çıkma

Korumalı alan eşzamanlı olarak çalışıyorsa Çalıştır işlevi yalnızca Korumalı Alan (Korumalı Alan) bittiğinde geri döner. Bu nedenle, fesih için ek bir adım gerekli değildir. Aşağıdaki kod snippet'i bu senaryoyu gösterir:

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

Eş zamansız olarak çalışan bir korumalı alandan çıkma

Korumalı alan eşzamansız olarak çalışıyorsa sonlandırma için iki seçenek mevcuttur. Öncelikle, Sandboxee'nin tamamlanmasını bekleyebilir ve son yürütme durumunu alabilirsiniz:

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

Alternatif olarak, Sandboxee'yi istediğiniz zaman sonlandırabilirsiniz ancak bu sırada başka bir nedenden dolayı Sandboxee'yi sonlandırabileceği için AwaitResult() çağrısı yapmanız önerilir:

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

7. Test etme

Diğer kodlarda olduğu gibi korumalı alan uygulamanızda testler bulunmalıdır. Korumalı alan testleri, programın doğruluğunu test etmek için değil, korumalı alana alınmış programın korumalı alan ihlalleri gibi sorunlar olmadan çalışıp çalışamayacağını kontrol etmek içindir. Bu, aynı zamanda korumalı alan politikasının doğru olduğundan emin olmanızı da sağlar.

Korumalı alan kapsamındaki bir program, üretimde çalıştırıldığı gibi, normalde işleyeceği bağımsız değişkenler ve giriş dosyalarıyla test edilir.

Bu testler, kabuk testi veya alt işlemler kullanan C++ testleri kadar basit olabilir. İlham almak için örneklere göz atın.

Sonuç

Şimdiye kadar okuduğunuz için teşekkür ederiz. Kılavuzumuzu beğendiğinizi ve artık kullanıcılarınızın güvenliğini sağlamaya yardımcı olmak için kendi korumalı alanlarınızı oluşturma gücüne sahip olduğunuzu umuyoruz.

Korumalı alanlar ve politikalar oluşturmak zor bir iştir ve göze çarpmayan hatalara açıktır. Risk almamak için politika ve kodunuzu bir güvenlik uzmanına gözden geçirmenizi öneririz.