Sindbad~EG File Manager
#ifndef TASK_FINDER_VMA_C
#define TASK_FINDER_VMA_C
#include <linux/file.h>
#include <linux/list.h>
#include <linux/jhash.h>
#include <linux/fs.h>
#include <linux/dcache.h>
#include "stp_helper_lock.h"
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,12,0)
static inline bool atomic_try_cmpxchg(atomic_t *v, int *old, int new)
{
int r, o = *old;
r = atomic_cmpxchg(v, o, new);
if (unlikely(r != o))
*old = r;
return likely(r == o);
}
#endif
#ifndef STAPCONF_HLIST_ADD_TAIL_RCU
// Added in linux 4.7, backported to rhel 7, not present in rhel 6
#define hlist_first_rcu(head) (*((struct hlist_node __rcu **)(&(head)->first)))
#define hlist_next_rcu(node) (*((struct hlist_node __rcu **)(&(node)->next)))
static inline void hlist_add_tail_rcu(struct hlist_node *n,
struct hlist_head *h)
{
struct hlist_node *i, *last = NULL;
for (i = hlist_first_rcu(h); i; i = hlist_next_rcu(i))
last = i;
if (last) {
n->next = last->next;
n->pprev = &last->next;
rcu_assign_pointer(hlist_next_rcu(last), n);
} else {
hlist_add_head_rcu(n, h);
}
}
#endif
#ifndef STAPCONF_ATOMIC_FETCH_ADD_UNLESS
static inline int atomic_fetch_add_unless(atomic_t *v, int a, int u)
{
int c = atomic_read(v);
do {
if (unlikely(c == u))
break;
} while (!atomic_try_cmpxchg(v, &c, c + a));
return c;
}
#endif
#ifndef __STP_TF_HASH_BITS
#define __STP_TF_HASH_BITS 8
#endif
#define __STP_TF_TABLE_SIZE (1 << __STP_TF_HASH_BITS)
#ifndef TASK_FINDER_VMA_ENTRY_PATHLEN
#define TASK_FINDER_VMA_ENTRY_PATHLEN 64
#elif TASK_FINDER_VMA_ENTRY_PATHLEN < 8
#error "gimme a little more TASK_FINDER_VMA_ENTRY_PATHLEN"
#endif
struct __stp_tf_vma_entry {
struct hlist_node hlist;
struct rcu_head rcu;
atomic_t refcount;
struct task_struct *tsk;
unsigned long vm_start;
unsigned long vm_end;
unsigned long offset; //offset from base addr of the module
char path[TASK_FINDER_VMA_ENTRY_PATHLEN]; /* mmpath name, if known */
// User data (possibly stp_module)
void *user;
};
struct __stp_tf_vma_bucket {
struct hlist_head head;
stp_spinlock_t lock;
};
static struct __stp_tf_vma_bucket *__stp_tf_vma_map;
// __stp_tf_vma_new_entry(): Returns an newly allocated or NULL.
// Must only be called from user context.
// ... except, with inode-uprobes / task-finder2, it can be called from
// random tracepoints. So we cannot sleep after all.
static struct __stp_tf_vma_entry *
__stp_tf_vma_new_entry(void)
{
struct __stp_tf_vma_entry *entry;
// Alloc using kmalloc rather than the stp variant. This way the RCU
// callback freeing the entries will not depend on using a function
// within this module to free the allocated memory (_stp_kfree), which
// lets us omit a costly rcu_barrier operation upon module unload.
#ifdef CONFIG_UTRACE
entry = kmalloc(sizeof(*entry), STP_ALLOC_SLEEP_FLAGS);
#else
entry = kmalloc(sizeof(*entry), STP_ALLOC_FLAGS);
#endif
return entry;
}
#ifndef kfree_rcu
static void __stp_tf_vma_free_entry(struct rcu_head *rcu)
{
struct __stp_tf_vma_entry *entry = container_of(rcu, typeof(*entry), rcu);
kfree(entry);
}
#endif
// __stp_tf_vma_put_entry(): Put a specified number of references on the entry.
static void
__stp_tf_vma_put_entry(struct __stp_tf_vma_bucket *bucket,
struct __stp_tf_vma_entry *entry, int count)
{
unsigned long flags;
int old;
// We must atomically subtract only if the refcount is non-zero, as well
// as check to see if the new refcount is zero, in which case we should
// free the entry.
old = atomic_fetch_add_unless(&entry->refcount, -count, 0);
if (old - count)
return;
stp_spin_lock_irqsave(&bucket->lock, flags);
hlist_del_rcu(&entry->hlist);
stp_spin_unlock_irqrestore(&bucket->lock, flags);
#ifdef kfree_rcu
kfree_rcu(entry, rcu);
#else
call_rcu(&entry->rcu, __stp_tf_vma_free_entry);
#endif
}
// stap_initialize_vma_map(): Initialize the free list. Grabs the
// spinlock. Should be called before any of the other stap_*_vma_map
// functions. Since this is run before any other function is called,
// this doesn't need any locking. Should be called from a user context
// since it can allocate memory.
static int
stap_initialize_vma_map(void)
{
struct __stp_tf_vma_bucket *buckets;
int i;
buckets = _stp_kmalloc_gfp(sizeof(*buckets) * __STP_TF_TABLE_SIZE,
STP_ALLOC_SLEEP_FLAGS);
if (!buckets)
return -ENOMEM;
for (i = 0; i < __STP_TF_TABLE_SIZE; i++) {
struct __stp_tf_vma_bucket *bucket = &buckets[i];
INIT_HLIST_HEAD(&bucket->head);
stp_spin_lock_init(&bucket->lock);
}
__stp_tf_vma_map = buckets;
return 0;
}
// stap_destroy_vma_map(): Unconditionally destroys vma entries.
// Nothing should be using it anymore.
static void
stap_destroy_vma_map(void)
{
int i;
if (!__stp_tf_vma_map)
return;
for (i = 0; i < __STP_TF_TABLE_SIZE; i++) {
struct __stp_tf_vma_bucket *bucket = &__stp_tf_vma_map[i];
struct __stp_tf_vma_entry *entry;
struct hlist_node *node;
rcu_read_lock();
stap_hlist_for_each_entry_rcu(entry, node, &bucket->head, hlist)
__stp_tf_vma_put_entry(bucket, entry, 1);
rcu_read_unlock();
}
_stp_kfree(__stp_tf_vma_map);
}
// __stp_tf_vma_bucket(): Get the bucket that should contain the task.
static inline struct __stp_tf_vma_bucket *
__stp_tf_get_vma_bucket(struct task_struct *tsk)
{
return &__stp_tf_vma_map[hash_ptr(tsk, __STP_TF_HASH_BITS)];
}
// Get vma entry if the vma is present in the vma map hash table satisfying the
// given condition.
#define __stp_tf_get_vma_map(bucket, tsk, acquire, condition) \
({ \
struct __stp_tf_vma_entry *entry, *found = NULL; \
struct hlist_node *node; \
\
rcu_read_lock(); \
stap_hlist_for_each_entry_rcu(entry, node, &bucket->head, hlist) { \
if (entry->tsk == tsk && (condition) && \
atomic_add_unless(&entry->refcount, acquire, 0)) { \
found = entry; \
break; \
} \
} \
rcu_read_unlock(); \
\
found; \
})
// Add the vma info to the vma map hash table.
// Caller is responsible for name lifetime.
// Can allocate memory, so needs to be called
// only from user context.
static int
stap_add_vma_map_info(struct task_struct *tsk, unsigned long vm_start,
unsigned long vm_end, unsigned long offset,
const char *path, void *user)
{
struct __stp_tf_vma_bucket *bucket = __stp_tf_get_vma_bucket(tsk);
struct __stp_tf_vma_entry *entry;
struct hlist_node *node;
unsigned long flags;
size_t path_len;
// Check if the entry already exists
if (__stp_tf_get_vma_map(bucket, tsk, 0, entry->vm_start == vm_start))
return -EEXIST;
entry = __stp_tf_vma_new_entry();
if (!entry)
return -ENOMEM;
// Fill in the new entry
entry->refcount = (atomic_t)ATOMIC_INIT(1);
entry->tsk = tsk;
entry->vm_start = vm_start;
entry->vm_end = vm_end;
entry->offset = offset;
entry->user = user;
path_len = strlen(path);
if (path_len >= TASK_FINDER_VMA_ENTRY_PATHLEN - 3) {
strlcpy(entry->path, "...", TASK_FINDER_VMA_ENTRY_PATHLEN);
strlcpy(entry->path + 3,
&path[path_len - TASK_FINDER_VMA_ENTRY_PATHLEN + 4],
TASK_FINDER_VMA_ENTRY_PATHLEN - 3);
} else {
strlcpy(entry->path, path, TASK_FINDER_VMA_ENTRY_PATHLEN);
}
stp_spin_lock_irqsave(&bucket->lock, flags);
hlist_add_tail_rcu(&entry->hlist, &bucket->head);
stp_spin_unlock_irqrestore(&bucket->lock, flags);
return 0;
}
// Extend the vma info vm_end in the vma map hash table if there is already
// a vma_info which ends precisely where this new one starts for the given
// task. Returns zero on success, -ESRCH if no existing matching entry could
// be found.
static int
stap_extend_vma_map_info(struct task_struct *tsk, unsigned long vm_start,
unsigned long vm_end)
{
struct __stp_tf_vma_bucket *bucket = __stp_tf_get_vma_bucket(tsk);
struct __stp_tf_vma_entry *entry;
entry = __stp_tf_get_vma_map(bucket, tsk, 1, entry->vm_end == vm_start);
if (!entry)
return -ESRCH;
entry->vm_end = vm_end;
__stp_tf_vma_put_entry(bucket, entry, 1);
return 0;
}
// Remove the vma entry from the vma hash table.
// Returns -ESRCH if the entry isn't present.
static int
stap_remove_vma_map_info(struct task_struct *tsk, unsigned long vm_start)
{
struct __stp_tf_vma_bucket *bucket = __stp_tf_get_vma_bucket(tsk);
struct __stp_tf_vma_entry *entry;
entry = __stp_tf_get_vma_map(bucket, tsk, 1, entry->vm_start == vm_start);
if (!entry)
return -ESRCH;
// Put two references: one for the reference we just got,
// and another to free the entry.
__stp_tf_vma_put_entry(bucket, entry, 2);
return 0;
}
// Finds vma info if the vma is present in the vma map hash table for
// a given task and address (between vm_start and vm_end).
// Returns -ESRCH if not present.
static int
stap_find_vma_map_info(struct task_struct *tsk, unsigned long addr,
unsigned long *vm_start, unsigned long *vm_end,
unsigned long *offset, const char **path, void **user)
{
struct __stp_tf_vma_bucket *bucket;
struct __stp_tf_vma_entry *entry;
if (!__stp_tf_vma_map)
return -ESRCH;
bucket = __stp_tf_get_vma_bucket(tsk);
entry = __stp_tf_get_vma_map(bucket, tsk, 1, addr >= entry->vm_start &&
addr <= entry->vm_end);
if (!entry)
return -ESRCH;
if (vm_start)
*vm_start = entry->vm_start;
if (vm_end)
*vm_end = entry->vm_end;
if (offset)
*offset = entry->offset;
if (path)
*path = entry->path;
if (user)
*user = entry->user;
__stp_tf_vma_put_entry(bucket, entry, 1);
return 0;
}
// Finds vma info if the vma is present in the vma map hash table for
// a given task with the given user handle.
// Returns -ESRCH if not present.
static int
stap_find_vma_map_info_user(struct task_struct *tsk, void *user,
unsigned long *vm_start, unsigned long *vm_end,
const char **path)
{
struct __stp_tf_vma_bucket *bucket;
struct __stp_tf_vma_entry *entry;
if (!__stp_tf_vma_map)
return -ESRCH;
bucket = __stp_tf_get_vma_bucket(tsk);
entry = __stp_tf_get_vma_map(bucket, tsk, 1, entry->user == user);
if (!entry)
return -ESRCH;
if (vm_start)
*vm_start = entry->vm_start;
if (vm_end)
*vm_end = entry->vm_end;
if (path)
*path = entry->path;
__stp_tf_vma_put_entry(bucket, entry, 1);
return 0;
}
static int
stap_drop_vma_maps(struct task_struct *tsk)
{
struct __stp_tf_vma_bucket *bucket = __stp_tf_get_vma_bucket(tsk);
struct __stp_tf_vma_entry *entry;
struct hlist_node *node;
rcu_read_lock();
stap_hlist_for_each_entry_rcu(entry, node, &bucket->head, hlist) {
if (entry->tsk == tsk)
__stp_tf_vma_put_entry(bucket, entry, 1);
}
rcu_read_unlock();
return 0;
}
/*
* stap_find_exe_file - acquire a reference to the mm's executable file
*
* Returns NULL if mm has no associated executable file. User must
* release file via fput().
*/
static struct file*
stap_find_exe_file(struct mm_struct* mm)
{
// The following kernel commit changed the way the exported
// get_mm_exe_file() works. This commit first appears in the
// 4.1 kernel:
//
// commit 90f31d0ea88880f780574f3d0bb1a227c4c66ca3
// Author: Konstantin Khlebnikov <khlebnikov@yandex-team.ru>
// Date: Thu Apr 16 12:47:56 2015 -0700
//
// mm: rcu-protected get_mm_exe_file()
//
// This patch removes mm->mmap_sem from mm->exe_file read side.
// Also it kills dup_mm_exe_file() and moves exe_file
// duplication into dup_mmap() where both mmap_sems are
// locked.
//
// So, for kernels >= 4.1, we'll use get_mm_exe_file(). For
// kernels < 4.1 but with get_mm_exe_file() exported, we'll
// still use our own code. The original get_mm_exe_file() can
// sleep (since it calls down_read()), so we'll have to roll
// our own.
//
// Some old kernels have the above kernel commit backported, in which
// case it's preferable to make use of the RCU optimization to avoid the
// failure-prone down_read_trylock(). The commit that adds the RCU
// optimization also adds a get_file_rcu() macro, so we can just check
// for the existence of that on kernels < 4.1. A false negative this way
// just leads to using the down_read_trylock() fallback as usual.
#if defined(get_file_rcu) || LINUX_VERSION_CODE >= KERNEL_VERSION(4,1,0)
#ifdef STAPCONF_GET_MM_EXE_FILE_EXPORTED
return get_mm_exe_file(mm);
#else
typedef typeof(&get_mm_exe_file) get_mm_exe_file_fn;
if (kallsyms_get_mm_exe_file == NULL)
return NULL; /* can't happen; _stp_handle_start would abort before this point */
else
return (* (get_mm_exe_file_fn) kallsyms_get_mm_exe_file)(mm);
#endif
#else
struct file *exe_file = NULL;
// The down_read() function can sleep, so we'll call
// down_read_trylock() instead, which can fail. If it
// fails, we'll just pretend this task didn't have a
// exe file.
if (mm && down_read_trylock(&mm->mmap_sem)) {
// VM_EXECUTABLE was killed in kernel commit e9714acf,
// but in kernels that new we can just use
// mm->exe_file anyway. (PR14712)
#ifdef VM_EXECUTABLE
struct vm_area_struct *vma;
for (vma = mm->mmap; vma; vma = vma->vm_next) {
if ((vma->vm_flags & VM_EXECUTABLE) && vma->vm_file) {
exe_file = vma->vm_file;
break;
}
}
#else
exe_file = mm->exe_file;
#endif
if (exe_file)
get_file(exe_file);
up_read(&mm->mmap_sem);
}
return exe_file;
#endif
}
#endif /* TASK_FINDER_VMA_C */
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists