diff options
Diffstat (limited to 'kernel/sys.c')
| -rw-r--r-- | kernel/sys.c | 145 | 
1 files changed, 145 insertions, 0 deletions
diff --git a/kernel/sys.c b/kernel/sys.c index 2bbd9a73b54..1c9090bc674 100644 --- a/kernel/sys.c +++ b/kernel/sys.c @@ -42,6 +42,8 @@  #include <linux/syscore_ops.h>  #include <linux/version.h>  #include <linux/ctype.h> +#include <linux/mm.h> +#include <linux/mempolicy.h>  #include <linux/compat.h>  #include <linux/syscalls.h> @@ -2099,6 +2101,146 @@ static int prctl_get_tid_address(struct task_struct *me, int __user **tid_addr)  }  #endif + +static int prctl_update_vma_anon_name(struct vm_area_struct *vma, +		struct vm_area_struct **prev, +		unsigned long start, unsigned long end, +		const char __user *name_addr) +{ +	struct mm_struct * mm = vma->vm_mm; +	int error = 0; +	pgoff_t pgoff; + +	if (name_addr == vma_get_anon_name(vma)) { +		*prev = vma; +		goto out; +	} + +	pgoff = vma->vm_pgoff + ((start - vma->vm_start) >> PAGE_SHIFT); +	*prev = vma_merge(mm, *prev, start, end, vma->vm_flags, vma->anon_vma, +				vma->vm_file, pgoff, vma_policy(vma), +				name_addr); +	if (*prev) { +		vma = *prev; +		goto success; +	} + +	*prev = vma; + +	if (start != vma->vm_start) { +		error = split_vma(mm, vma, start, 1); +		if (error) +			goto out; +	} + +	if (end != vma->vm_end) { +		error = split_vma(mm, vma, end, 0); +		if (error) +			goto out; +	} + +success: +	if (!vma->vm_file) +		vma->shared.anon_name = name_addr; + +out: +	if (error == -ENOMEM) +		error = -EAGAIN; +	return error; +} + +static int prctl_set_vma_anon_name(unsigned long start, unsigned long end, +			unsigned long arg) +{ +	unsigned long tmp; +	struct vm_area_struct * vma, *prev; +	int unmapped_error = 0; +	int error = -EINVAL; + +	/* +	 * If the interval [start,end) covers some unmapped address +	 * ranges, just ignore them, but return -ENOMEM at the end. +	 * - this matches the handling in madvise. +	 */ +	vma = find_vma_prev(current->mm, start, &prev); +	if (vma && start > vma->vm_start) +		prev = vma; + +	for (;;) { +		/* Still start < end. */ +		error = -ENOMEM; +		if (!vma) +			return error; + +		/* Here start < (end|vma->vm_end). */ +		if (start < vma->vm_start) { +			unmapped_error = -ENOMEM; +			start = vma->vm_start; +			if (start >= end) +				return error; +		} + +		/* Here vma->vm_start <= start < (end|vma->vm_end) */ +		tmp = vma->vm_end; +		if (end < tmp) +			tmp = end; + +		/* Here vma->vm_start <= start < tmp <= (end|vma->vm_end). */ +		error = prctl_update_vma_anon_name(vma, &prev, start, end, +				(const char __user *)arg); +		if (error) +			return error; +		start = tmp; +		if (prev && start < prev->vm_end) +			start = prev->vm_end; +		error = unmapped_error; +		if (start >= end) +			return error; +		if (prev) +			vma = prev->vm_next; +		else	/* madvise_remove dropped mmap_sem */ +			vma = find_vma(current->mm, start); +	} +} + +static int prctl_set_vma(unsigned long opt, unsigned long start, +		unsigned long len_in, unsigned long arg) +{ +	struct mm_struct *mm = current->mm; +	int error; +	unsigned long len; +	unsigned long end; + +	if (start & ~PAGE_MASK) +		return -EINVAL; +	len = (len_in + ~PAGE_MASK) & PAGE_MASK; + +	/* Check to see whether len was rounded up from small -ve to zero */ +	if (len_in && !len) +		return -EINVAL; + +	end = start + len; +	if (end < start) +		return -EINVAL; + +	if (end == start) +		return 0; + +	down_write(&mm->mmap_sem); + +	switch (opt) { +	case PR_SET_VMA_ANON_NAME: +		error = prctl_set_vma_anon_name(start, end, arg); +		break; +	default: +		error = -EINVAL; +	} + +	up_write(&mm->mmap_sem); + +	return error; +} +  SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,  		unsigned long, arg4, unsigned long, arg5)  { @@ -2226,6 +2368,9 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,  			else  				return -EINVAL;  			break; +		case PR_SET_VMA: +			error = prctl_set_vma(arg2, arg3, arg4, arg5); +			break;  		default:  			return -EINVAL;  		}  |