לדלג לתוכן

אבטחה בקרנל - תרגול

תרגיל 1 - בדיקת מצב אבטחה

בצעו את הפקודות הבאות על מכונת לינוקס (או VM) ותעדו את התוצאות:

א. בדקו מה מצב ה-ASLR:

cat /proc/sys/kernel/randomize_va_space

ב. הריצו את הפקודה הבאה פעמיים וכתבו מה ההבדל:

cat /proc/self/maps | head -3

ג. בדקו אילו capabilities יש לתהליך הנוכחי שלכם:

cat /proc/self/status | grep Cap

פענחו את השורה CapEff עם capsh --decode=<value>.

ד. בדקו אם KASLR פעיל:

sudo cat /proc/kallsyms | grep ' T _text'

רשמו את הכתובת. הפעילו מחדש ובדקו שוב - האם הכתובת השתנתה?

ה. בדקו אם יש תוכניות setuid במערכת:

find /usr/bin -perm -4000 -type f 2>/dev/null

רשמו לפחות 3 תוכניות ותסבירו למה הן צריכות setuid.


תרגיל 2 - capabilities

א. הסבירו מה ההבדל בין 4 סטי ה-capabilities הבאים:
- CapInh (inherited)
- CapPrm (permitted)
- CapEff (effective)
- CapBnd (bounding)

ב. נניח שאתם רוצים להריץ שרת web (nginx) שמאזין על פורט 80, אבל בלי להריץ אותו כ-root. אילו capabilities הייתם נותנים לו? כתבו את הפקודה.

ג. למה CAP_SYS_ADMIN נחשב ל-"the new root"? מה הבעיה עם ה-capability הזו?


תרגיל 3 - seccomp

קראו את הקוד הבא וענו על השאלות:

#include <stdio.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <linux/seccomp.h>
#include <linux/filter.h>
#include <linux/audit.h>
#include <sys/syscall.h>

void install_filter(void)
{
    struct sock_filter filter[] = {
        // Load syscall number
        BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)),
        // Allow read (0)
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_read, 0, 1),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
        // Allow write (1)
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_write, 0, 1),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
        // Allow exit_group (231)
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_exit_group, 0, 1),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
        // Kill on anything else
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),
    };

    struct sock_fprog prog = {
        .len = sizeof(filter) / sizeof(filter[0]),
        .filter = filter,
    };

    prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
    prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog);
}

int main(void)
{
    install_filter();

    // After this point, only read, write, exit_group are allowed
    write(1, "Hello!\n", 7);

    // What happens here?
    FILE *f = fopen("/etc/passwd", "r");  // Line A
    printf("f = %p\n", f);               // Line B

    return 0;
}

א. אילו syscalls מותרים אחרי install_filter()?

ב. מה יקרה בשורה A (fopen)? למה?

ג. האם שורה B תתבצע? למה?

ד. מה תפקיד PR_SET_NO_NEW_PRIVS? למה הוא נדרש לפני seccomp?


תרגיל 4 - namespaces

א. הריצו את הפקודה הבאה וכתבו מה קורה:

sudo unshare --pid --fork --mount-proc bash

בתוך ה-shell החדש, הריצו ps aux. מה אתם רואים? למה?

ב. הריצו:

sudo unshare --net bash

בתוך ה-shell החדש, הריצו ip addr. מה קרה לממשקי הרשת? האם אתם יכולים לגלוש לאינטרנט מתוך ה-shell הזה?

ג. הסבירו למה user namespaces חשובים לאבטחת קונטיינרים. מה קורה אם תהליך בתוך קונטיינר רץ כ-root (UID 0)?

ד. Docker משתמש בשילוב של namespaces, cgroups, seccomp, ו-capabilities. לכל אחד מהתרחישים הבאים, זהו איזה מנגנון אחראי:
1. קונטיינר לא יכול לראות תהליכים של קונטיינרים אחרים
2. קונטיינר מוגבל ל-512MB זיכרון
3. קונטיינר לא יכול לקרוא ל-reboot() syscall
4. קונטיינר יש לו כתובת IP משלו
5. תהליך root בקונטיינר לא יכול לטעון מודולי קרנל


תרגיל 5 - ניתוח חולשות

לכל אחד מקטעי הקוד הבאים, זהו את החולשה, הסבירו איך אפשר לנצל אותה, ואיזה מנגנוני הגנה יכולים למנוע את הניצול.

קטע א - syscall handler

static long my_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    char buffer[256];
    struct user_request req;

    if (copy_from_user(&req, (void __user *)arg, sizeof(req)))
        return -EFAULT;

    // req.len is user-controlled!
    if (copy_from_user(buffer, req.data_ptr, req.len))
        return -EFAULT;

    process_data(buffer, req.len);
    return 0;
}

קטע ב - reference counting

struct my_device {
    struct kref refcount;
    void (*cleanup)(struct my_device *);
    char name[32];
};

static void release_device(struct kref *kref)
{
    struct my_device *dev = container_of(kref, struct my_device, refcount);
    dev->cleanup(dev);
    kfree(dev);
}

static int device_close(struct inode *inode, struct file *file)
{
    struct my_device *dev = file->private_data;
    kref_put(&dev->refcount, release_device);
    return 0;
}

static ssize_t device_read(struct file *file, char __user *buf,
                            size_t count, loff_t *pos)
{
    struct my_device *dev = file->private_data;
    // Bug: what if device was already freed?
    return copy_to_user(buf, dev->name, min(count, sizeof(dev->name)));
}

קטע ג - race condition

static long check_and_read(const char __user *filename, char __user *buf, size_t len)
{
    struct kstat stat;

    // Step 1: Check if user can read the file
    if (vfs_stat(filename, &stat))
        return -ENOENT;

    if (stat.uid.val != current_uid().val)
        return -EACCES;

    // Step 2: Read the file
    // Time-of-check to time-of-use (TOCTOU)!
    return kernel_read_file(filename, buf, len);
}