shell bypass 403

Cubjrnet7 Shell


name : lsm_pre_events.c
/**
@file     lsm_pre_events.c
@brief    LSM based hooks
@details  Copyright (c) 2024 Acronis International GmbH
@author   Bruce Wang ([email protected])
@since    $Id: $
*/

#ifdef CONFIG_SECURITY

#include <linux/list.h>
#include <linux/mman.h>
#include <linux/net.h>
#include <linux/types.h>

#include "compat.h"
#include "file_handle_tools.h"
#include "ftrace_hooks/fsnotify_listener.h"
#include "ftrace_hooks/ftrace_events.h"
#include "fs_event.h"
#include "lsm_common.h"
#include "memory.h"
#include "path_tools.h"
#include "si_templates.h"
#include "si_writer_common.h"
#include "subtype.h"
#include "syscall_common.h"
#include "task_info_map.h"
#include "task_tools.h"
#include "transport.h"
#include "transport_protocol.h"
#include "write_protection.h"

static int _lsm_file_open(struct file *file)
{
    long ret = 0;
    const struct path *path;
    task_info_t *task_info;
    transport_ids_t transport_ids;
    file_context_info_t file_context_info = {0};
    const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_SYNC_FILE_PRE_OPEN)
                                       | MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_PRE_OPEN);
    subtypes_t generatedSubtypes;

    if (!file)
    {
        return 0;
    }

#ifdef FMODE_NONOTIFY
    if (file->f_mode & FMODE_NONOTIFY)
        return 0;
#endif

    if (!HOOK_PROLOG())
    {
        return 0;
    }

    path = &file->f_path;
    if (path->mnt && sb_ok(path->mnt->mnt_sb))
        fsnotify_events_listen_sb(path->mnt->mnt_sb);

    if (!(transport_global_get_combined_mask() & (generatedEventsMask | MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_PROCESS_EXEC))))
        goto out;

    generatedSubtypes = fs_open_subtypes(file->f_flags, path->dentry->d_inode);
    if (!transport_global_subtype_needed(generatedSubtypes.sync)
     && !transport_global_subtype_needed(generatedSubtypes.notify))
        goto out;

    if (transport_process_belongs_to_control(current))
        goto out;

    task_info = task_info_map_get(current);
    if (!task_info)
    {
        goto out;
    }
    transport_global_get_ids(&transport_ids);
    refresh_task(task_info, &transport_ids);

    if (!path_is_valid(path))
    {
        DPRINTF("path is not valid");
        goto err_exit;
    }

    if (make_file_context_info(&file_context_info, path, file->f_flags, 0, 0, READ_ONCE(task_info->pid_version)) &&
        check_open_cache(&transport_ids, &file_context_info))
    {
        goto err_exit;
    }

    if (!task_info_can_skip(task_info, &transport_ids, generatedEventsMask))
    {
        long block = fs_event_pre_open(task_info, file->f_flags, path, &file_context_info.msg_info);
        // TODO: Why is block changed to EACCES?
        ret = block ? -EACCES : 0;
        DPRINTF("finished 'fs_event_pre_open(flags=0o%o/0x%x)", file->f_flags, file->f_flags);
    }

err_exit:
    task_info_put(task_info);

out:
    HOOK_EPILOG();

    return ret;
}

#ifdef HAVE_FILE_OPEN
#pragma message("HAVE_FILE_OPEN")
#ifdef FILE_OPEN_WITH_CRED
#pragma message("FILE_OPEN_WITH_CRED")
static int lsm_file_open(struct file *file, const struct cred *cred)
{
    (void) cred;
    return _lsm_file_open(file);
}
#else
static int lsm_file_open(struct file *file)
{
    return _lsm_file_open(file);
}
#endif

#else
static int lsm_dentry_open(struct file *file, const struct cred *cred)
{
    (void) cred;
    return _lsm_file_open(file);
}
#endif

#ifdef HAVE_MMAP_FILE
static int lsm_mmap_file(struct file *file, unsigned long reqprot, unsigned long prot, unsigned long flags)
{
    long ret = 0;
    const struct path *path;
    task_info_t *task_info;
    transport_ids_t transport_ids;
    const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_SYNC_FILE_PRE_MMAP)
                                       | MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_PRE_MMAP);
    const bool writable = (flags & MAP_SHARED) && (prot & PROT_WRITE);
    subtypes_t subtypes;

    if (!file)
    {
        return 0;
    }

    if (!file_is_valid(file))
    {
        return 0;
    }

#ifdef FMODE_NONOTIFY
    if (file->f_mode & FMODE_NONOTIFY)
        return 0;
#endif

    if (!HOOK_PROLOG())
    {
        return 0;
    }

    if (!(transport_global_get_combined_mask() & generatedEventsMask))
        goto out;

    path = &file->f_path;
    subtypes = fs_mmap_subtypes(writable, path->dentry->d_inode);
    if (!transport_global_subtype_needed(subtypes.notify)
     && !transport_global_subtype_needed(subtypes.sync))
        goto out;
    if (transport_process_belongs_to_control(current))
        goto out;

    task_info = task_info_map_get(current);
    if (!task_info)
    {
        goto out;
    }
    transport_global_get_ids(&transport_ids);
    refresh_task(task_info, &transport_ids);

    if (!task_info_can_skip(task_info, &transport_ids, generatedEventsMask))
    {
        long block = fs_event_pre_mmap(task_info, file->f_flags, path, reqprot, prot, flags);
        // TODO: Why is block changed to EACCES?
        ret = block ? -EACCES : 0;
        DPRINTF("finished 'fs_event_pre_mmap(flags=0o%o/0x%x)", file->f_flags, file->f_flags);
    }

    task_info_put(task_info);

out:
    HOOK_EPILOG();

    return ret;
}
#endif

#ifdef HAVE_CONST_PATH_IN_SECURITY
#define LSM_PATH_PTR const struct path*
#else
#define LSM_PATH_PTR struct path*
#endif

#ifdef CONFIG_SECURITY_PATH
static int _lsm_path_rename(const struct path *old_dir, struct dentry *old_dentry,
                            const struct path *new_dir, struct dentry *new_dentry,
                            unsigned int flags)

{
    long ret = 0;
    struct path oldpath = (struct path){};
    struct path newpath = (struct path){};
    task_info_t *task_info;
    transport_ids_t transport_ids;
    bool new_path_is_valid = false;
    const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_SYNC_FILE_PRE_RENAME)
                                       | MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_PRE_RENAME);

    if (!old_dir || !old_dentry ||
        !new_dir || !new_dentry)
    {
        return 0;
    }

    if (!HOOK_PROLOG())
    {
        return 0;
    }

    if (!(transport_global_get_combined_mask() & generatedEventsMask) ||
        transport_process_belongs_to_control(current))
    {
        goto out;
    }

    task_info = task_info_map_get(current);
    if (!task_info)
    {
        goto out;
    }
    transport_global_get_ids(&transport_ids);
    refresh_task(task_info, &transport_ids);

    oldpath.mnt = old_dir->mnt;
    oldpath.dentry = old_dentry;
    if (!path_is_valid(&oldpath))
    {
        DPRINTF("path is invalid");
        goto err_exit;
    }

    // TODO: There should be a check for negative dentry
    newpath.mnt = new_dir->mnt;
    newpath.dentry = new_dentry;
    new_path_is_valid = path_is_valid(&newpath);

    fsnotify_events_listen_sb(oldpath.mnt->mnt_sb);

    DPRINTF("calling fs_event_pre_rename(flags=%u)", flags);
    if (!task_info_can_skip(task_info, &transport_ids, generatedEventsMask))
    {
        long block = fs_event_pre_rename(task_info, flags, &oldpath, &newpath, new_path_is_valid);
        ret = block ? -EACCES : 0;
    }
    DPRINTF("finished 'fs_event_pre_rename(flags=%u)", flags);

err_exit:
    task_info_put(task_info);

out:
    HOOK_EPILOG();

    return ret;
}

#ifdef RENAME_HAS_FLAGS
static int lsm_path_rename(LSM_PATH_PTR old_dir, struct dentry *old_dentry,
                           LSM_PATH_PTR new_dir, struct dentry *new_dentry,
                           unsigned int flags)
{
    return _lsm_path_rename(old_dir, old_dentry, new_dir, new_dentry, flags);
}
#else
static int lsm_path_rename(LSM_PATH_PTR old_dir, struct dentry *old_dentry,
                           LSM_PATH_PTR new_dir, struct dentry *new_dentry)
{
    return _lsm_path_rename(old_dir, old_dentry, new_dir, new_dentry, 0);
}
#endif

static int handle_unlink(const struct path *dir, struct dentry *dentry, int flag)
{
    long ret = 0;
    struct path path = (struct path){};
    task_info_t *task_info;
    transport_ids_t transport_ids;
    const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_SYNC_FILE_PRE_UNLINK)
                                       | MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_PRE_UNLINK);

    if (!dir || !dentry)
    {
        return 0;
    }

    if (!HOOK_PROLOG())
    {
        return 0;
    }

    if (!(transport_global_get_combined_mask() & generatedEventsMask) ||
        transport_process_belongs_to_control(current))
    {
        goto out;
    }

    task_info = task_info_map_get(current);
    if (!task_info)
    {
        goto out;
    }
    transport_global_get_ids(&transport_ids);
    refresh_task(task_info, &transport_ids);

    path.mnt = dir->mnt;
    path.dentry = dentry;
    if (!path_is_valid(&path))
    {
        DPRINTF("path is invalid");
        goto err_exit;
    }

    fsnotify_events_listen_sb(path.mnt->mnt_sb);

    DPRINTF("calling fs_event_pre_unlink(flag=%d)", flag);
    if (!task_info_can_skip(task_info, &transport_ids, generatedEventsMask))
    {
        long block = fs_event_pre_unlink(task_info, &path);
        ret = block ? -EACCES : 0;
    }
    DPRINTF("finished 'fs_event_pre_unlink(flag=%d)", flag);

err_exit:
    task_info_put(task_info);

out:
    HOOK_EPILOG();
    return ret;
}

static int lsm_path_unlink(LSM_PATH_PTR dir, struct dentry *dentry)
{
    return handle_unlink(dir, dentry, 0);
}

static int lsm_path_rmdir(LSM_PATH_PTR dir, struct dentry *dentry)
{
    return handle_unlink(dir, dentry, AT_REMOVEDIR);
}

static int lsm_path_link(struct dentry *old_dentry, LSM_PATH_PTR new_dir, struct dentry *new_dentry)
{
    long ret = 0;
    struct path oldpath = (struct path){};
    struct path newpath = (struct path){};
    task_info_t *task_info;
    transport_ids_t transport_ids;
    const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_SYNC_FILE_PRE_LINK)
                                       | MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_PRE_LINK);

    if (!old_dentry || !new_dir || !new_dentry)
    {
        return 0;
    }

    if (!HOOK_PROLOG())
    {
        return 0;
    }

    if (!(transport_global_get_combined_mask() & generatedEventsMask) ||
        transport_process_belongs_to_control(current))
    {
        goto out;
    }

    task_info = task_info_map_get(current);
    if (!task_info)
    {
        goto out;
    }
    transport_global_get_ids(&transport_ids);
    refresh_task(task_info, &transport_ids);

    oldpath.mnt = new_dir->mnt;
    oldpath.dentry = old_dentry;
    if (!path_is_valid(&oldpath))
    {
        DPRINTF("path is invalid");
        goto err_exit;
    }

    newpath.mnt = new_dir->mnt;
    newpath.dentry = new_dentry;

    DPRINTF("calling fs_event_pre_link()");
    if (!task_info_can_skip(task_info, &transport_ids, generatedEventsMask))
    {
        long block = fs_event_pre_link(task_info, &oldpath, &newpath);
        ret = block ? -EACCES : 0;
    }
    DPRINTF("finished 'fs_event_pre_link()");

err_exit:
    task_info_put(task_info);

out:
    HOOK_EPILOG();

    return ret;
}

static int lsm_path_chmod(LSM_PATH_PTR path, umode_t mode)
{
    task_info_t *task_info;
    transport_ids_t transport_ids;
    const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_CHMOD);

    if (!path || !path_is_valid(path))
    {
        return 0;
    }

    if (!HOOK_PROLOG())
    {
        return 0;
    }

    if (!(transport_global_get_combined_mask() & generatedEventsMask) ||
        transport_process_belongs_to_control(current))
    {
        goto out;
    }

    task_info = task_info_map_get(current);
    if (!task_info)
    {
        goto out;
    }
    transport_global_get_ids(&transport_ids);
    refresh_task(task_info, &transport_ids);
    if (!task_info_can_skip(task_info, &transport_ids, generatedEventsMask))
    {
        fs_event_pre_chmod(task_info, path, mode);
    }

    task_info_put(task_info);

out:
    HOOK_EPILOG();

    return 0;
}

static int lsm_path_chown(LSM_PATH_PTR path, COMPAT_KUID_T uid, COMPAT_KGID_T gid)
{
    task_info_t *task_info;
    transport_ids_t transport_ids;
    const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_CHOWN);

    if (!path || !path_is_valid(path))
    {
        return 0;
    }

    if (!HOOK_PROLOG())
    {
        return 0;
    }

    if (!(transport_global_get_combined_mask() & generatedEventsMask) ||
        transport_process_belongs_to_control(current))
    {
        goto out;
    }

    task_info = task_info_map_get(current);
    if (!task_info)
    {
        goto out;
    }
    transport_global_get_ids(&transport_ids);
    refresh_task(task_info, &transport_ids);
    if (!task_info_can_skip(task_info, &transport_ids, generatedEventsMask))
    {
        fs_event_pre_chown(task_info, path, from_kuid_compat(uid), from_kgid_compat(gid));
    }

    task_info_put(task_info);

out:
    HOOK_EPILOG();

    return 0;
}

static int lsm_path_mkdir(LSM_PATH_PTR dir, struct dentry *dentry, umode_t mode)
{
    task_info_t *task_info;
    transport_ids_t transport_ids;
    struct path path;
    const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_MKDIR);

    if (!dir || !dentry)
    {
        return 0;
    }

    if (!path_is_valid(dir))
    {
        return 0;
    }

    if (!HOOK_PROLOG())
    {
        return 0;
    }

    // Mind that 'path' will refer to 'negative' dentry so do not attempt to check it for validity
    path.mnt = dir->mnt;
    fsnotify_events_listen_sb(path.mnt->mnt_sb);
    path.dentry = dentry;
    if (!(transport_global_get_combined_mask() & generatedEventsMask) ||
        transport_process_belongs_to_control(current))
    {
        goto out;
    }

    task_info = task_info_map_get(current);
    if (!task_info)
    {
        goto out;
    }

    transport_global_get_ids(&transport_ids);
    refresh_task(task_info, &transport_ids);
    if (!task_info_can_skip(task_info, &transport_ids, generatedEventsMask))
    {
        fs_event_mkdir(task_info, &path, mode);
    }

    task_info_put(task_info);

out:
    HOOK_EPILOG();

    return 0;
}
#endif

#if defined(CONFIG_SECURITY_PATH) || defined(HAVE_FILE_TRUNCATE)
static int lsm_path_truncate(LSM_PATH_PTR path)
{
    long ret = 0;
    task_info_t *task_info;
    transport_ids_t transport_ids;
    const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_SYNC_FILE_PRE_TRUNCATE)
                                       | MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_PRE_TRUNCATE);

    if (!path)
    {
        return 0;
    }

    if (!HOOK_PROLOG())
    {
        return 0;
    }

    if (!(transport_global_get_combined_mask() & generatedEventsMask) ||
        transport_process_belongs_to_control(current))
    {
        goto out;
    }

    task_info = task_info_map_get(current);
    if (!task_info)
    {
        goto out;
    }

    transport_global_get_ids(&transport_ids);
    refresh_task(task_info, &transport_ids);

    if (!path_is_valid(path))
    {
        goto err_exit;
    }

    DPRINTF("calling fs_event_pre_truncate()");
    if (!task_info_can_skip(task_info, &transport_ids, generatedEventsMask))
    {
        long block = fs_event_pre_truncate(task_info, path);
        ret = block ? -EACCES : 0;
    }
    DPRINTF("finished 'fs_event_pre_truncate()");

err_exit:
    task_info_put(task_info);

out:
    HOOK_EPILOG();
    return ret;
}
#endif

#ifdef HAVE_FILE_TRUNCATE
static int lsm_file_truncate(struct file *file)
{
    if (!file)
        return 0;

#ifdef FMODE_NONOTIFY
    if (file->f_mode & FMODE_NONOTIFY)
        return 0;
#endif

    return lsm_path_truncate(&file->f_path);
}
#endif

static int lsm_settime(const TIMESPEC *ts, const struct timezone *tz)
{
    static const uint8_t k_set_time_fields[] = { SI_COMMON_FIELDS, FP_SI_PI_SYSTEM_TIME_OLD_TIMESTAMP, FP_SI_PI_SYSTEM_TIME_NEW_TIMESTAMP };
    transport_ids_t transport_ids;
    task_info_t *task_info;
    const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_SETTIME);

    (void) tz;
    if (!ts)
    {
        return 0;
    }

    if (!HOOK_PROLOG())
    {
        return 0;
    }

    if (!(transport_global_get_combined_mask() & generatedEventsMask) ||
        transport_process_belongs_to_control(current))
    {
        goto out;
    }

    task_info = task_info_map_get(current);
    if (!task_info)
    {
        goto out;
    }

    transport_global_get_ids(&transport_ids);
    if (!task_info_can_skip(task_info, &transport_ids, generatedEventsMask))
    {
        uint64_t unique_pid = make_unique_pid(current);
        uint64_t event_uid;
        uint32_t event_size;
        msg_t *msg = NULL;
        event_size = SI_ESTIMATE_SIZE_CONST_PARAMS(k_set_time_fields);
        msg = msg_new(FP_SI_OT_NOTIFY_SETTIME, 0, SI_CT_PRE_CALLBACK, unique_pid, event_size);
        if (!msg)
            goto err_exit;

        event_uid = transport_global_sequence_next();

        {
            SiTimeMicroseconds us;
            si_property_writer_t writer;
            si_event_writer_init(&writer, &msg->event, event_size);
            si_property_writer_write_common(&writer, event_uid, current->pid, current->tgid, task_info);
            us.microseconds = ktime_to_us(ktime_get_real());
            si_property_writer_write_system_time_old_timestamp(&writer, us);
            us.microseconds = ts->tv_sec * 1000000 + ts->tv_nsec / 1000;
            si_property_writer_write_system_time_new_timestamp(&writer, us);
            si_event_writer_finalize(&msg->event, &writer);
        }

        msg->task_info = task_info_get(task_info);
        msg->id = event_uid;

        send_msg_async_unref_unchecked(msg);
    }

err_exit:
    task_info_put(task_info);

out:
    HOOK_EPILOG();
    return 0;
}

#ifdef HAVE_FSNOTIFY_PUBLIC_API
static int lsm_inode_create(struct inode *dir, struct dentry *dentry, umode_t mode)
{
    (void) mode;
    if (!HOOK_PROLOG())
        return 0;

    if (dir && sb_ok(dir->i_sb))
        fsnotify_events_listen_sb(dir->i_sb);
    if (dentry && sb_ok(dentry->d_sb))
        fsnotify_events_listen_sb(dentry->d_sb);

    HOOK_EPILOG();
    return 0;
}
#endif

#if defined(HAVE_SECURITY_HOOK_LIST) || defined(HAVE_LSM_STATIC_CALL)
// MARK: Page RO unprotect
// Must be at least 6x amount of callbacks in lsm_defs_x.h. Currently 256 is enough
// The most amount of pages are altered during hooklist: head+next+prev unprotect across page boundary
#define MAX_UNPROTECTED_PAGES 256
typedef struct
{
    struct page* page;
    unsigned long address;
    bool changed;
} unprotected_page_t;

static unprotected_page_t s_unprotected_pages[MAX_UNPROTECTED_PAGES];
static unsigned int s_unprotected_pages_count = 0;
#ifndef HAVE_SECURITY_HOOK_LIST
// When hook list is not used, pages can be static because they never change.
// Unfortunately, this is not the case with hook list because it can be changed at runtime (although unlikely)
// For safety sake, pages will be recollected every time.
static bool s_all_pages_collected = 0;
#endif

static void unprotect(void* ptr)
{
    unsigned long address = (((unsigned long) ptr) / PAGE_SIZE) * PAGE_SIZE;
    unsigned int level;
    struct page *page;
    int unprotected_idx;
    int i;

#ifndef HAVE_SECURITY_HOOK_LIST
    if (s_all_pages_collected)
        return;
#endif

    for (i = 0; i < s_unprotected_pages_count; i++)
        if (address == s_unprotected_pages[i].address)
            return;

    page = (struct page *) lookup_address(address, &level);
    if (!page)
        return;

    unprotected_idx = s_unprotected_pages_count++;
    s_unprotected_pages[unprotected_idx].page = page;
    s_unprotected_pages[unprotected_idx].address = address;
    if (!test_bit(_PAGE_BIT_RW, &page->flags)) {
        set_bit(_PAGE_BIT_RW, &page->flags);
        s_unprotected_pages[unprotected_idx].changed = true;
    } else {
        s_unprotected_pages[unprotected_idx].changed = false;
    }
}

static void do_unprotect_struct(void* ptr, size_t sz)
{
    unprotect(ptr);
    unprotect((char*) ptr + sz);
}

#define UNPROTECT_STRUCT(ptr) do_unprotect_struct(&(ptr), sizeof(ptr))

static void unprotect_all(void)
{
    int i;
    for (i = 0; i < s_unprotected_pages_count; i++) {
        if (s_unprotected_pages[i].changed)
            set_bit(_PAGE_BIT_RW, &s_unprotected_pages[i].page->flags);
    }
}

static void reprotect_all(void)
{
    int i;
    for (i = 0; i < s_unprotected_pages_count; i++) {
        if (s_unprotected_pages[i].changed)
           clear_bit(_PAGE_BIT_RW, &s_unprotected_pages[i].page->flags);
    }

#ifndef HAVE_SECURITY_HOOK_LIST
    s_all_pages_collected = 1;
#else
    s_unprotected_pages_count = 0;
#endif
}
#endif

#ifdef HAVE_SECURITY_HOOK_LIST
#include <linux/lsm_hooks.h>
#include <linux/rcupdate.h>

typedef struct
{
    struct security_hook_list file_open;
    struct security_hook_list mmap_file;
    struct security_hook_list settime;
    struct security_hook_list path_rename;
    struct security_hook_list path_unlink;
    struct security_hook_list path_rmdir;
    struct security_hook_list path_link;
    struct security_hook_list path_chmod;
    struct security_hook_list path_chown;
    struct security_hook_list path_mkdir;
    struct security_hook_list path_truncate;
#ifdef HAVE_FILE_TRUNCATE
    struct security_hook_list file_truncate;
#endif
#ifdef HAVE_FSNOTIFY_PUBLIC_API
    struct security_hook_list inode_create;
#endif
} lsm_hook_list_t;

static lsm_hook_list_t lsm_hook_list = {
    .file_open = {
        .hook.file_open = lsm_file_open,
    },
    .mmap_file = {
        .hook.mmap_file = lsm_mmap_file,
    },
    .settime = {
        .hook.settime = lsm_settime,
    },
#ifdef CONFIG_SECURITY_PATH
    .path_rename = {
        .hook.path_rename = lsm_path_rename,
    },
    .path_unlink = {
        .hook.path_unlink = lsm_path_unlink,
    },
    .path_rmdir = {
        .hook.path_rmdir = lsm_path_rmdir,
    },
    .path_link = {
        .hook.path_link = lsm_path_link,
    },
    .path_chmod = {
        .hook.path_chmod = lsm_path_chmod,
    },
    .path_chown = {
        .hook.path_chown = lsm_path_chown,
    },
    .path_mkdir = {
        .hook.path_mkdir = lsm_path_mkdir,
    },
    .path_truncate = {
        .hook.path_truncate = lsm_path_truncate,
    },
#endif
#ifdef HAVE_FILE_TRUNCATE
    .file_truncate = {
        .hook.file_truncate = lsm_file_truncate,
    },
#endif
#ifdef HAVE_FSNOTIFY_PUBLIC_API
    .inode_create = {
        .hook.inode_create = lsm_inode_create,
    },
#endif
};

#endif

#if defined(HAVE_SECURITY_HOOK_LIST) && !defined(HAVE_LSM_STATIC_CALL)

/*
The original func to add lsm hooks, the lsm name and lsm_append() is not mandatory
void __init security_add_hooks(struct security_hook_list *hooks, int count,
                   const char *lsm)
{
    int i;

    for (i = 0; i < count; i++) {
        hooks[i].lsm = lsm;
        hlist_add_tail_rcu(&hooks[i].list, hooks[i].head);
    }

    if (slab_is_available()) {
        if (lsm_append(lsm, &lsm_names) < 0)
            panic("%s - Cannot get early memory.\n", __func__);
    }
}
*/

#ifdef HOOK_LIST_USE_HLIST

static void unprotect_hlist_node(struct hlist_node* node)
{        
    UNPROTECT_STRUCT(*node);
    if (node->pprev)
        unprotect(node->pprev);
}

#define add_hook_to_list_head(hook_list, security_hook_heads_addr, hook_name)            \
    UNPROTECT_STRUCT(security_hook_heads_addr->hook_name); \
    if (security_hook_heads_addr->hook_name.first) \
        unprotect_hlist_node(security_hook_heads_addr->hook_name.first); \
    hlist_add_head_rcu(&hook_list.hook_name.list, &security_hook_heads_addr->hook_name); \
    print_hooks(security_hook_heads_addr, hook_name);
#define remove_hook(hook_list, hook_name) \
    if (hook_list.hook_name.list.pprev) \
        unprotect(hook_list.hook_name.list.pprev); \
    if (hook_list.hook_name.list.next) \
        unprotect_hlist_node(hook_list.hook_name.list.next); \
    hlist_del_rcu(&hook_list.hook_name.list)

#else

#define add_hook_to_list_head(hook_list, security_hook_heads_addr, hook_name)           \
    UNPROTECT_STRUCT(security_hook_heads_addr->hook_name); \
    if (security_hook_heads_addr->hook_name.next) \
        UNPROTECT_STRUCT(*security_hook_heads_addr->hook_name.next); \
    if (security_hook_heads_addr->hook_name.prev) \
        UNPROTECT_STRUCT(*security_hook_heads_addr->hook_name.prev); \
    list_add_rcu(&hook_list.hook_name.list, &security_hook_heads_addr->hook_name); \
    print_hooks(security_hook_heads_addr, hook_name);
#define remove_hook(hook_list, hook_name) \
    if (hook_list.hook_name.list.next) \
        UNPROTECT_STRUCT(*hook_list.hook_name.list.next); \
    if (hook_list.hook_name.list.prev) \
        UNPROTECT_STRUCT(*hook_list.hook_name.list.prev); \
    list_del_rcu(&hook_list.hook_name.list)

#endif

static int _register_lsm_pre_events(void)
{
    struct security_hook_heads *security_hook_heads_addr = (struct security_hook_heads *)compat_kallsyms_lookup_name("security_hook_heads");
    if (security_hook_heads_addr == NULL)
    {
        return -1;
    }
    print_address_symbol(security_hook_heads_addr);
    {
        unsigned long flags;
        cr0_write_protect_t wp;
        local_irq_save(flags);
        wp = disable_write_protect();
        {
            unprotect_all();

            if (ftrace_post_event_have(FTRACE_POST_EVENT_FSNOTIFY))
            {
                add_hook_to_list_head(lsm_hook_list, security_hook_heads_addr, file_open);
                IPRINTF("LSM %s was planted!", "open");
            }
            add_hook_to_list_head(lsm_hook_list, security_hook_heads_addr, mmap_file);
            IPRINTF("LSM %s was planted!", "mmap");
            add_hook_to_list_head(lsm_hook_list, security_hook_heads_addr, settime);
            IPRINTF("LSM %s was planted!", "settime");
#ifdef CONFIG_SECURITY_PATH
            if (ftrace_post_event_have(FTRACE_POST_EVENT_FSNOTIFY))
            {
                add_hook_to_list_head(lsm_hook_list, security_hook_heads_addr, path_rename);
                IPRINTF("LSM %s was planted!", "rename");
                add_hook_to_list_head(lsm_hook_list, security_hook_heads_addr, path_unlink);
                IPRINTF("LSM %s was planted!", "unlink");
                add_hook_to_list_head(lsm_hook_list, security_hook_heads_addr, path_rmdir);
                IPRINTF("LSM %s was planted!", "rmdir");
            }
            add_hook_to_list_head(lsm_hook_list, security_hook_heads_addr, path_link);
            IPRINTF("LSM %s was planted!", "link");
            add_hook_to_list_head(lsm_hook_list, security_hook_heads_addr, path_chmod);
            IPRINTF("LSM %s was planted!", "chmod");
            add_hook_to_list_head(lsm_hook_list, security_hook_heads_addr, path_chown);
            IPRINTF("LSM %s was planted!", "chown");
            add_hook_to_list_head(lsm_hook_list, security_hook_heads_addr, path_mkdir);
            IPRINTF("LSM %s was planted!", "mkdir");
            add_hook_to_list_head(lsm_hook_list, security_hook_heads_addr, path_truncate);
            IPRINTF("LSM %s was planted!", "truncate");
#endif
#ifdef HAVE_FILE_TRUNCATE
            add_hook_to_list_head(lsm_hook_list, security_hook_heads_addr, file_truncate);
            IPRINTF("LSM %s was planted!", "file_truncate");
#endif
#ifdef HAVE_FSNOTIFY_PUBLIC_API
            add_hook_to_list_head(lsm_hook_list, security_hook_heads_addr, inode_create);
            IPRINTF("LSM %s was planted!", "inode_create");
#endif
            reprotect_all();
        }
        restore_write_protect(wp);
        local_irq_restore(flags);
    }
    return 0;
}

static void _unregister_lsm_pre_events(void)
{
    unsigned long flags;
    cr0_write_protect_t wp;
    local_irq_save(flags);
    wp = disable_write_protect();
    {
        unprotect_all();

        if (ftrace_post_event_have(FTRACE_POST_EVENT_FSNOTIFY))
        {
            remove_hook(lsm_hook_list, file_open);
            IPRINTF("LSM %s was removed!", "open");
        }
        remove_hook(lsm_hook_list, mmap_file);
        IPRINTF("LSM %s was removed!", "mmap");
        remove_hook(lsm_hook_list, settime);
        IPRINTF("LSM %s was removed!", "settime");
#ifdef CONFIG_SECURITY_PATH
        if (ftrace_post_event_have(FTRACE_POST_EVENT_FSNOTIFY))
        {
            remove_hook(lsm_hook_list, path_rename);
            IPRINTF("LSM %s was removed!", "rename");
            remove_hook(lsm_hook_list, path_unlink);
            IPRINTF("LSM %s was removed!", "unlink");
            remove_hook(lsm_hook_list, path_rmdir);
            IPRINTF("LSM %s was removed!", "rmdir");
        }
        remove_hook(lsm_hook_list, path_link);
        IPRINTF("LSM %s was removed!", "link");
        remove_hook(lsm_hook_list, path_chmod);
        IPRINTF("LSM %s was removed!", "chmod");
        remove_hook(lsm_hook_list, path_chown);
        IPRINTF("LSM %s was removed!", "chown");
        remove_hook(lsm_hook_list, path_mkdir);
        IPRINTF("LSM %s was removed!", "mkdir");
        remove_hook(lsm_hook_list, path_truncate);
        IPRINTF("LSM %s was removed!", "truncate");
#endif
#ifdef HAVE_FILE_TRUNCATE
        remove_hook(lsm_hook_list, file_truncate);
        IPRINTF("LSM %s was removed!", "file_truncate");
#endif
#ifdef HAVE_FSNOTIFY_PUBLIC_API
        remove_hook(lsm_hook_list, inode_create);
        IPRINTF("LSM %s was removed!", "inode_create");
#endif
        reprotect_all();
    }

    restore_write_protect(wp);
    local_irq_restore(flags);
    synchronize_rcu();
}

#else

#define MAP0(m,...)
#define MAP1(m,t,a,...) m(t,a)
#define MAP2(m,t,a,...) m(t,a), MAP1(m,__VA_ARGS__)
#define MAP3(m,t,a,...) m(t,a), MAP2(m,__VA_ARGS__)
#define MAP4(m,t,a,...) m(t,a), MAP3(m,__VA_ARGS__)
#define MAP5(m,t,a,...) m(t,a), MAP4(m,__VA_ARGS__)
#define MAP6(m,t,a,...) m(t,a), MAP5(m,__VA_ARGS__)
#define MAP7(m,t,a,...) m(t,a), MAP6(m,__VA_ARGS__)
#define MAP8(m,t,a,...) m(t,a), MAP7(m,__VA_ARGS__)
#define MAP9(m,t,a,...) m(t,a), MAP8(m,__VA_ARGS__)
#define MAP10(m,t,a,...) m(t,a), MAP9(m,__VA_ARGS__)
#define MAP11(m,t,a,...) m(t,a), MAP10(m,__VA_ARGS__)
#define MAP(n,...) MAP##n(__VA_ARGS__)

#define SC_DECL(t, a) t a
#define SC_ARGS(t, a) a

#ifndef HAVE_LSM_STATIC_CALL
#include <linux/security.h>
struct security_operations **security_ops_addr;
static struct security_operations lsm_security_ops;
static struct security_operations *ori_security_ops;
#define LSM_HOOK_ENTER(name, args_cnt, ...) \
static inline void* ori_get_##name(void) { return (void*) ori_security_ops->name; }
#include "lsm_defs_x.h"
#else
#include <linux/lsm_hooks.h>
struct lsm_static_calls_table* lsm_static_calls;

#define LSM_HOOK_ENTER(name, args_cnt, ...) \
static void* lsm_ori_##name; \
static inline void* ori_get_##name(void) { return lsm_ori_##name; } \
struct security_hook_list* lsm_ori_hl_##name; \
static int lsm_planted_slot_##name;
#include "lsm_defs_x.h"
#endif

#include "hook_trampoline_common.h"

#define LSM_HOOK_TRAMPOLINE(name) lsm_##name##_trampoline
#define LSM_HOOK_NAME(name) lsm_##name##_enter
#define HOOK_TRAMPOLINE(name, ...) HOOK_TRAMPOLINE_ASM(LSM_HOOK_TRAMPOLINE(name), LSM_HOOK_NAME(name)) \
int LSM_HOOK_TRAMPOLINE(name) (__VA_ARGS__);
#define HOOK_TRAMPOLINE_NO_RET(name, ...) HOOK_TRAMPOLINE_ASM(LSM_HOOK_TRAMPOLINE(name), LSM_HOOK_NAME(name)) \
void LSM_HOOK_TRAMPOLINE(name) (__VA_ARGS__);

#define LSM_HOOK_ENTER(name, args_cnt, ...) \
hook_ret_t lsm_##name##_enter(MAP(args_cnt, SC_DECL, __VA_ARGS__)); \
hook_ret_t lsm_##name##_enter(MAP(args_cnt, SC_DECL, __VA_ARGS__)) \
{ \
    hook_ret_t ret; \
    ret.ret = lsm_##name(MAP(args_cnt, SC_ARGS, __VA_ARGS__)); \
    if (0 == ret.ret) \
        ret.fn = (long) ori_get_##name(); \
    else \
        ret.fn = 0; \
    return ret; \
} \
HOOK_TRAMPOLINE(name, MAP(args_cnt, SC_DECL, __VA_ARGS__))

#define LSM_HOOK_ENTER_NO_RET(name, args_cnt, ...) \
hook_ret_t lsm_##name##_enter(MAP(args_cnt, SC_DECL, __VA_ARGS__)); \
hook_ret_t lsm_##name##_enter(MAP(args_cnt, SC_DECL, __VA_ARGS__)) \
{ \
    hook_ret_t ret; \
    lsm_##name(MAP(args_cnt, SC_ARGS, __VA_ARGS__)); \
    ret.ret = 0; \
    ret.fn = (long) ori_get_##name(); \
    return ret; \
} \
HOOK_TRAMPOLINE_NO_RET(name, MAP(args_cnt, SC_DECL, __VA_ARGS__))

// Ignore the ENOSYS result and just jump to the original function
#define LSM_HOOK_ENTER_ENOSYS(name, args_cnt, ...) \
hook_ret_t lsm_##name##_enter(MAP(args_cnt, SC_DECL, __VA_ARGS__)); \
hook_ret_t lsm_##name##_enter(MAP(args_cnt, SC_DECL, __VA_ARGS__)) \
{ \
    hook_ret_t ret; \
    (void) lsm_##name(MAP(args_cnt, SC_ARGS, __VA_ARGS__)); \
    ret.ret = 0; \
    ret.fn = (long) ori_get_##name(); \
    return ret; \
} \
HOOK_TRAMPOLINE(name, MAP(args_cnt, SC_DECL, __VA_ARGS__))

#include "lsm_defs_x.h"

#ifndef HAVE_LSM_STATIC_CALL
static void fixup_lsm_ops(struct security_operations *security_ops)
{
    lsm_security_ops = *security_ops;

    // TODO: If we use LSM inotify_create, fsnotify restriction can be lifted
    if (ftrace_post_event_have(FTRACE_POST_EVENT_FSNOTIFY))
    {
#ifdef HAVE_FILE_OPEN
        lsm_security_ops.file_open = lsm_file_open_trampoline;
#else
        lsm_security_ops.dentry_open = lsm_dentry_open_trampoline;
#endif
        IPRINTF("LSM %s was planted!", "open");
    }

#ifdef HAVE_MMAP_FILE
    lsm_security_ops.mmap_file = lsm_mmap_file_trampoline;
    IPRINTF("LSM %s was planted!", "mmap");
#endif

    lsm_security_ops.settime = lsm_settime_trampoline;
    IPRINTF("LSM %s was planted!", "settime");

#ifdef CONFIG_SECURITY_PATH
    if (ftrace_post_event_have(FTRACE_POST_EVENT_FSNOTIFY))
    {
        lsm_security_ops.path_rename = lsm_path_rename_trampoline;
        IPRINTF("LSM %s was planted!", "rename");
        lsm_security_ops.path_unlink = lsm_path_unlink_trampoline;
        IPRINTF("LSM %s was planted!", "unlink");
        lsm_security_ops.path_rmdir = lsm_path_rmdir_trampoline;
        IPRINTF("LSM %s was planted!", "rmdir");
    }
    lsm_security_ops.path_truncate = lsm_path_truncate_trampoline;
    IPRINTF("LSM %s was planted!", "truncate");
    lsm_security_ops.path_link = lsm_path_link_trampoline;
    IPRINTF("LSM %s was planted!", "link");
    lsm_security_ops.path_chmod = lsm_path_chmod_trampoline;
    IPRINTF("LSM %s was planted!", "chmod");
    lsm_security_ops.path_chown = lsm_path_chown_trampoline;
    IPRINTF("LSM %s was planted!", "chown");
    lsm_security_ops.path_mkdir = lsm_path_mkdir_trampoline;
    IPRINTF("LSM %s was planted!", "mkdir");
#endif

#ifdef HAVE_FSNOTIFY_PUBLIC_API
    lsm_security_ops.inode_create = lsm_inode_create_trampoline;
    IPRINTF("LSM %s was planted!", "inode_create");
#endif
}

static int _register_lsm_pre_events(void)
{
    security_ops_addr = (struct security_operations **)compat_kallsyms_lookup_name("security_ops");
    ori_security_ops = *security_ops_addr;
    if (security_ops_addr == NULL || ori_security_ops == NULL)
    {
        EPRINTF("%p %p", security_ops_addr, ori_security_ops);
        return -1;
    }
    print_address_symbol(security_ops_addr);
    print_address_symbol(ori_security_ops);

    fixup_lsm_ops(*security_ops_addr);

    {
        unsigned long flags;
        local_irq_save(flags);
        {
            cr0_write_protect_t wp = disable_write_protect();

            *security_ops_addr = &lsm_security_ops;

            restore_write_protect(wp);
        }
        local_irq_restore(flags);
    }
    return 0;
}

static void _unregister_lsm_pre_events(void)
{
    if (security_ops_addr == NULL || ori_security_ops == NULL)
    {
        EPRINTF("%p %p", security_ops_addr, ori_security_ops);
        return;
    }
    {
        unsigned long flags;
        local_irq_save(flags);
        {
            cr0_write_protect_t wp = disable_write_protect();

            *security_ops_addr = ori_security_ops;

            restore_write_protect(wp);
        }
        local_irq_restore(flags);
    }
    IPRINTF("LSM was unregistered");
}
#else
// This function assumes the following features of static calls:
// 1) There is 'func' in the 'key' that can be pulled out safely
// 2) '__static_call_update' exists to allow safely installing the hook

// Search for an empty slot and place our hook in there.
// If slot is not found, just place the hook at the first entry with trampoline.

// Note that for some reason there is both static_calls AND original hook lists are used.
// Most functions use static_call as normal but 'prctl' in particular uses an old method.
// I assume eventually 'lsm_hook_list' usage will be dropped but for now, setup both hooks.

#define LSM_BUSY_SLOT (-1)

#define add_slot(name) \
    do { \
        int i; \
        bool found_empty_slot = false; \
        void* fn; \
        for (i = 0; i < MAX_LSM_COUNT - 1; i++) \
            if (!static_key_enabled(&lsm_static_calls->name[i].active->key)) { \
                found_empty_slot = true; break; \
            } \
        if (!found_empty_slot) i = 0; \
        lsm_planted_slot_##name = found_empty_slot ? i : LSM_BUSY_SLOT; \
        lsm_ori_##name = (void*) lsm_static_calls->name[i].key->func; \
        lsm_ori_hl_##name = lsm_static_calls->name[i].hl; \
        fn = found_empty_slot ? (void*) lsm_##name : (void*) lsm_##name##_trampoline; \
        lsm_hook_list.name.hook.name = fn; \
        unprotect(&lsm_static_calls->name[i].hl); \
        lsm_static_calls->name[i].hl = &lsm_hook_list.name; \
        UNPROTECT_STRUCT(lsm_static_calls->name[i].key); \
        restore_write_protect(wp); \
        local_irq_restore(flags); \
        __static_call_update(lsm_static_calls->name[i].key, lsm_static_calls->name[i].trampoline, fn); \
        local_irq_save(flags); \
        redisable_write_protect(wp); \
        if (found_empty_slot) \
            static_key_enable(&lsm_static_calls->name[i].active->key); \
        IPRINTF("LSM %s was planted at slot %d!", #name, lsm_planted_slot_##name); \
    } while (0)

#define remove_slot(name) \
    do { \
        int i = lsm_planted_slot_##name == LSM_BUSY_SLOT ? 0 : lsm_planted_slot_##name; \
        if (lsm_planted_slot_##name != LSM_BUSY_SLOT) \
            static_key_disable(&lsm_static_calls->name[i].active->key); \
        unprotect(&lsm_static_calls->name[i].hl); \
        lsm_static_calls->name[i].hl = lsm_ori_hl_##name; \
        UNPROTECT_STRUCT(lsm_static_calls->name[i].key); \
        restore_write_protect(wp); \
        local_irq_restore(flags); \
        __static_call_update(lsm_static_calls->name[i].key, lsm_static_calls->name[i].trampoline, lsm_ori_##name); \
        local_irq_save(flags); \
        redisable_write_protect(wp); \
        IPRINTF("LSM %s was removed!", #name); \
    } while (0)

static int _register_lsm_pre_events(void)
{
    lsm_static_calls = (struct lsm_static_calls_table*) compat_kallsyms_lookup_name("static_calls_table");
    if (lsm_static_calls == NULL)
    {
        return -1;
    }
    print_address_symbol(lsm_static_calls);
    {
        cr0_write_protect_t wp;
        unsigned long flags;
        local_irq_save(flags);
        wp = disable_write_protect();
        {
            unprotect_all();

            if (ftrace_post_event_have(FTRACE_POST_EVENT_FSNOTIFY))
            {
                add_slot(file_open);
            }
            add_slot(mmap_file);
            add_slot(settime);
#ifdef CONFIG_SECURITY_PATH
            if (ftrace_post_event_have(FTRACE_POST_EVENT_FSNOTIFY))
            {
                add_slot(path_rename);
                add_slot(path_unlink);
                add_slot(path_rmdir);
            }
            add_slot(path_link);
            add_slot(path_chmod);
            add_slot(path_chown);
            add_slot(path_mkdir);
            add_slot(path_truncate);
#endif
#ifdef HAVE_FILE_TRUNCATE
            add_slot(file_truncate);
#endif
#ifdef HAVE_FSNOTIFY_PUBLIC_API
            add_slot(inode_create);
#endif
            reprotect_all();
        }
        restore_write_protect(wp);
        local_irq_restore(flags);
    }
    return 0;
}

static void _unregister_lsm_pre_events(void)
{
    cr0_write_protect_t wp;
    unsigned long flags;

    if (!lsm_static_calls)
        return;

    local_irq_save(flags);
    wp = disable_write_protect();
    {
        unprotect_all();

        if (ftrace_post_event_have(FTRACE_POST_EVENT_FSNOTIFY))
        {
            remove_slot(file_open);
        }
        remove_slot(mmap_file);
        remove_slot(settime);
#ifdef CONFIG_SECURITY_PATH
        if (ftrace_post_event_have(FTRACE_POST_EVENT_FSNOTIFY))
        {
            remove_slot(path_rename);
            remove_slot(path_unlink);
            remove_slot(path_rmdir);
        }
        remove_slot(path_link);
        remove_slot(path_chmod);
        remove_slot(path_chown);
        remove_slot(path_mkdir);
        remove_slot(path_truncate);
#endif
#ifdef HAVE_FILE_TRUNCATE
        remove_slot(file_truncate);
#endif
#ifdef HAVE_FSNOTIFY_PUBLIC_API
        remove_slot(inode_create);
#endif
        reprotect_all();
    }

    restore_write_protect(wp);
    local_irq_restore(flags);
    synchronize_rcu();
    lsm_static_calls = NULL;
}
#endif
#endif

int register_lsm_pre_events(void)
{
    if (_register_lsm_pre_events() == 0)
    {
        return 0;
    }
    return -1;
}

void unregister_lsm_pre_events(void)
{
    _unregister_lsm_pre_events();
}

#else

int register_lsm_pre_events(void)
{
    return -1;
}

void unregister_lsm_pre_events(void)
{

}

#endif

© 2025 Cubjrnet7