Read the "real_parent" field of task_struct

John Wood john.wood at gmx.com
Thu Oct 1 13:49:02 EDT 2020


On Wed, Sep 30, 2020 at 07:59:47AM -0400, Valdis Klētnieks wrote:
> On Fri, 25 Sep 2020 18:11:42 +0200, John Wood said:
>
> > There are more examples but are similars to the ones showed. So my question
> > is how to read the "real_parent" field correctly. If I can understand all
> > the above examples I think I will have the knowledge to implement my LSM in
> > a correct way.
>
> There's multiple data structures which contain a pointer to the "real_parent"
> that we're interested in.  So depending on which data structure you're working
> with, you'll be following a different path to the real_parent, and depending on
> what locks (if any) the calling code already has, the access method may be
> different.

I want to check if the real_parent field of the task_struct structure is
NULL or not. More info below.

> It's similar to giving directions to where I used to live on Long Island. If
> you were coming from the Boston area, you wanted to take I-95 South, then
> either the Throg's Neck or Whitestone Bridge, get to I-495 (Long Island
> Expressway),  get off at a given exit, take that road south, and turn left to
> enter the neighborhood from the north, but if you're coming from New Jersey,
> you want to get to Staten Island, Verazanno Narrows Bridge, then Southern State
> Parkway to *its* exit for that same road going north, but the right turn  to enter the
> neighborhood from the south isn't the same one as if you're coming from the
> north (unless you prefer driving another 3/4 of a mile to turn at the same light
> as if you were coming from the north...)
>
> You end up at the same place, even though you're using different paths to get
> there.  The inside of the kernel works the same way.  Which way you get to
> 'real_parent' depends on what data structure you are already working with.

First of all, thanks for your comments. :)

Ok, I will show some parts of my code to clarify my question, and I will add
a small description of the idea behind.

Idea: The purpose of my LSM is to detect and mitigate a fork brute force
attack. To do so, I need a hierarchy of fork processes. In other words, there
is a pointer in the task_struct structure that points to an statistics data
structure. This pointer is copied to the child process when a process forks.
This way, all the tasks that fork with the same root share the same statistics.
These statistics allow to compute the application crashing period and detect
the attack.

So, all the tasks have an statistical data (copied from its parent). When an
execve is executed a new statistical data is allocated and a new hierarchy is
created.

But there is the case when the task 0 is allocated. In this case it's not
possible to copy the parent pointer to the statistical data. The task 0 don't
have parent. In this scenario a new statistics structure need to be allocated.

The idea exposed here is not completed but it's enought to clarify my
question. I hope.

So, my question is how to read the real_parent field of task_struct in this
situation in a secure way.

Below I show some part of the code to clarify:

/**
 * Target for task_alloc hook - Fork management.
 */
static int brute_task_alloc(struct task_struct *task, unsigned long clone_flags)
{
	struct brute_stats **stats, **p_stats;
	struct task_struct *p_task;

	stats = brute_stats_ptr(task);

	/*
	   Is this read correct? I need to use rcu_dereference?
	   I need to use a rcu_read_lock/rcu_read_unlock block?
	*/
	p_task = task->real_parent;			/////// This 3 lines are
							/////// the base of
	if (likely(p_task)) {				/////// my question to
		p_stats = brute_stats_ptr(p_task);	/////// the mailing list
		if (!*p_stats)
			return -EFAULT;

		spin_lock(&(*p_stats)->lock);
		refcount_inc(&(*p_stats)->refc);
		*stats = *p_stats;
		spin_unlock(&(*p_stats)->lock);

		return 0;
	}

	/* Task 0 stats allocation */
	*stats = brute_new_stats();
	if (!*stats)
		return -ENOMEM;

	return 0;
}

More code below to clarify structures and LSM hooks:

static struct security_hook_list brute_hooks[] __lsm_ro_after_init = {
	LSM_HOOK_INIT(task_alloc, brute_task_alloc),
	LSM_HOOK_INIT(bprm_committing_creds, brute_task_execve),
	LSM_HOOK_INIT(task_free, brute_task_free),
};

static int __init brute_init(void)
{
	pr_info("Brute initialized\n");
	security_add_hooks(brute_hooks, ARRAY_SIZE(brute_hooks),
			   KBUILD_MODNAME);
	return 0;
}

DEFINE_LSM(brute) = {
	.name = KBUILD_MODNAME,
	.init = brute_init,
	.blobs = &brute_blob_sizes,
};

/**
 * struct brute_stats - Fork brute force attack statistics.
 * @lock: Lock to protect the brute_stats structure.
 * @refc: Reference counter.
 * @timestamps: Last crashes timestamps list.
 * @timestamps_size: Last crashes timestamps list size.
 *
 * This structure holds the statistical data shared by all the fork hierarchy
 * processes.
 */
struct brute_stats {
	spinlock_t lock;
	refcount_t refc;
	struct list_head timestamps;
	unsigned char timestamps_size;
};

/**
 * struct brute_timestamp - Last crashes timestamps list entry.
 * @jiffies: Crash timestamp.
 * @node: Entry list head.
 *
 * This structure holds a crash timestamp.
 */
struct brute_timestamp {
	u64 jiffies;
	struct list_head node;
};

/**
 * brute_blob_sizes - LSM blob sizes.
 *
 * To share statistical data among all the fork hierarchy processes, define a
 * pointer to the brute_stats structure as a part of the task_struct's security
 * blob.
 */
static struct lsm_blob_sizes brute_blob_sizes __lsm_ro_after_init = {
	.lbs_task = sizeof(struct brute_stats *),
};

/**
 * brute_stats_ptr() - Get the pointer to the brute_stats stucture.
 * @task: Pointer to the task that holds the statistical data.
 *
 * Return: A pointer to a pointer to the brute_stats structure.
 */
static inline struct brute_stats **brute_stats_ptr(struct task_struct *task)
{
	return task->security + brute_blob_sizes.lbs_task;
}

/**
 * brute_new_timestamp() - Allocation of a new timestamp structure.
 *
 * If the allocation is successful the timestamp is set to now.
 *
 * Return: NULL if the allocation fails. A pointer to the new allocated
 *         timestamp structure if it success.
 */
static struct brute_timestamp *brute_new_timestamp(void)
{
	struct brute_timestamp *timestamp;

	timestamp = kmalloc(sizeof(struct brute_timestamp), GFP_KERNEL);
	if (!timestamp)
		return NULL;

	timestamp->jiffies = get_jiffies_64();
	INIT_LIST_HEAD(&timestamp->node);

	return timestamp;
}

/**
 * brute_new_stats() - Allocation of a new statistics structure.
 *
 * If the allocation is successful the reference counter is set to one to
 * indicate that there will be one task that points to this structure. The last
 * crashes timestamps list is initialized with one entry set to now. This way,
 * its possible to compute the application crash period with the first fault.
 *
 * Return: NULL if the allocation fails. A pointer to the new allocated
 *         statistics structure if it success.
 */
static struct brute_stats *brute_new_stats(void)
{
	struct brute_stats *stats;
	struct brute_timestamp *timestamp;

	stats = kmalloc(sizeof(struct brute_stats), GFP_KERNEL);
	if (!stats)
		return NULL;

	timestamp = brute_new_timestamp();
	if (!timestamp) {
		kfree(stats);
		return NULL;
	}

	spin_lock_init(&stats->lock);
	refcount_set(&stats->refc, 1);
	INIT_LIST_HEAD(&stats->timestamps);
	list_add(&timestamp->node, &stats->timestamps);
	stats->timestamps_size = 1;

	return stats;
}

I hope that now is clear what I want to do. Any help will be greatly
appreciated.

Thanks,
John Wood




More information about the Kernelnewbies mailing list