ptrace.c 4.42 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/*
 * linux/kernel/ptrace.c
 *
 * (C) Copyright 1999 Linus Torvalds
 *
 * Common interfaces for "ptrace()" which we do not want
 * to continually duplicate across every architecture.
 */

#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/highmem.h>
#include <linux/smp_lock.h>

#include <asm/pgtable.h>
#include <asm/uaccess.h>

Linus Torvalds's avatar
Linus Torvalds committed
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
/*
 * Check that we have indeed attached to the thing..
 */
int ptrace_check_attach(struct task_struct *child, int kill)
{
	if (!(child->ptrace & PT_PTRACED))
		return -ESRCH;

	if (child->p_pptr != current)
		return -ESRCH;

	if (!kill) {
		if (child->state != TASK_STOPPED)
			return -ESRCH;
#ifdef CONFIG_SMP
Linus Torvalds's avatar
Linus Torvalds committed
34
		wait_task_inactive(child);
Linus Torvalds's avatar
Linus Torvalds committed
35 36 37 38 39 40 41
#endif		
	}

	/* All systems go.. */
	return 0;
}

Linus Torvalds's avatar
Linus Torvalds committed
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
int ptrace_attach(struct task_struct *task)
{
	task_lock(task);
	if (task->pid <= 1)
		goto bad;
	if (task == current)
		goto bad;
	if (!task->mm)
		goto bad;
	if(((current->uid != task->euid) ||
	    (current->uid != task->suid) ||
	    (current->uid != task->uid) ||
 	    (current->gid != task->egid) ||
 	    (current->gid != task->sgid) ||
 	    (!cap_issubset(task->cap_permitted, current->cap_permitted)) ||
 	    (current->gid != task->gid)) && !capable(CAP_SYS_PTRACE))
		goto bad;
	rmb();
	if (!task->mm->dumpable && !capable(CAP_SYS_PTRACE))
		goto bad;
	/* the same process cannot be attached many times */
	if (task->ptrace & PT_PTRACED)
		goto bad;

	/* Go */
	task->ptrace |= PT_PTRACED;
Linus Torvalds's avatar
Linus Torvalds committed
68 69
	if (capable(CAP_SYS_PTRACE))
		task->ptrace |= PT_PTRACE_CAP;
Linus Torvalds's avatar
Linus Torvalds committed
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
	task_unlock(task);

	write_lock_irq(&tasklist_lock);
	if (task->p_pptr != current) {
		REMOVE_LINKS(task);
		task->p_pptr = current;
		SET_LINKS(task);
	}
	write_unlock_irq(&tasklist_lock);

	send_sig(SIGSTOP, task, 1);
	return 0;

bad:
	task_unlock(task);
	return -EPERM;
}

Linus Torvalds's avatar
Linus Torvalds committed
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
int ptrace_detach(struct task_struct *child, unsigned int data)
{
	if ((unsigned long) data > _NSIG)
		return	-EIO;

	/* Architecture-specific hardware disable .. */
	ptrace_disable(child);

	/* .. re-parent .. */
	child->ptrace = 0;
	child->exit_code = data;
	write_lock_irq(&tasklist_lock);
	REMOVE_LINKS(child);
	child->p_pptr = child->p_opptr;
	SET_LINKS(child);
	write_unlock_irq(&tasklist_lock);

	/* .. and wake it up. */
	wake_up_process(child);
	return 0;
}
Linus Torvalds's avatar
Linus Torvalds committed
109

Linus Torvalds's avatar
Linus Torvalds committed
110
/*
Linus Torvalds's avatar
Linus Torvalds committed
111 112 113
 * Access another process' address space.
 * Source/target buffer must be kernel space, 
 * Do not walk the page table directly, use get_user_pages
Linus Torvalds's avatar
Linus Torvalds committed
114 115 116 117 118
 */

int access_process_vm(struct task_struct *tsk, unsigned long addr, void *buf, int len, int write)
{
	struct mm_struct *mm;
Linus Torvalds's avatar
Linus Torvalds committed
119 120 121
	struct vm_area_struct *vma;
	struct page *page;
	void *old_buf = buf;
Linus Torvalds's avatar
Linus Torvalds committed
122 123 124 125 126 127 128 129 130 131

	/* Worry about races with exit() */
	task_lock(tsk);
	mm = tsk->mm;
	if (mm)
		atomic_inc(&mm->mm_users);
	task_unlock(tsk);
	if (!mm)
		return 0;

Linus Torvalds's avatar
Linus Torvalds committed
132
	down_read(&mm->mmap_sem);
Linus Torvalds's avatar
Linus Torvalds committed
133 134 135 136 137 138 139 140 141
	/* ignore errors, just check how much was sucessfully transfered */
	while (len) {
		int bytes, ret, offset;
		void *maddr;

		ret = get_user_pages(current, mm, addr, 1,
				write, 1, &page, &vma);
		if (ret <= 0)
			break;
Linus Torvalds's avatar
Linus Torvalds committed
142

Linus Torvalds's avatar
Linus Torvalds committed
143 144 145 146 147 148 149 150 151 152 153
		bytes = len;
		offset = addr & (PAGE_SIZE-1);
		if (bytes > PAGE_SIZE-offset)
			bytes = PAGE_SIZE-offset;

		flush_cache_page(vma, addr);

		maddr = kmap(page);
		if (write) {
			memcpy(maddr + offset, buf, bytes);
			flush_page_to_ram(page);
154
			flush_icache_user_range(vma, page, addr, bytes);
Linus Torvalds's avatar
Linus Torvalds committed
155 156 157 158 159 160 161 162 163
		} else {
			memcpy(buf, maddr + offset, bytes);
			flush_page_to_ram(page);
		}
		kunmap(page);
		put_page(page);
		len -= bytes;
		buf += bytes;
	}
Linus Torvalds's avatar
Linus Torvalds committed
164
	up_read(&mm->mmap_sem);
Linus Torvalds's avatar
Linus Torvalds committed
165
	mmput(mm);
Linus Torvalds's avatar
Linus Torvalds committed
166 167
	
	return buf - old_buf;
Linus Torvalds's avatar
Linus Torvalds committed
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
}

int ptrace_readdata(struct task_struct *tsk, unsigned long src, char *dst, int len)
{
	int copied = 0;

	while (len > 0) {
		char buf[128];
		int this_len, retval;

		this_len = (len > sizeof(buf)) ? sizeof(buf) : len;
		retval = access_process_vm(tsk, src, buf, this_len, 0);
		if (!retval) {
			if (copied)
				break;
			return -EIO;
		}
		if (copy_to_user(dst, buf, retval))
			return -EFAULT;
		copied += retval;
		src += retval;
		dst += retval;
		len -= retval;			
	}
	return copied;
}

int ptrace_writedata(struct task_struct *tsk, char * src, unsigned long dst, int len)
{
	int copied = 0;

	while (len > 0) {
		char buf[128];
		int this_len, retval;

		this_len = (len > sizeof(buf)) ? sizeof(buf) : len;
		if (copy_from_user(buf, src, this_len))
			return -EFAULT;
		retval = access_process_vm(tsk, dst, buf, this_len, 1);
		if (!retval) {
			if (copied)
				break;
			return -EIO;
		}
		copied += retval;
		src += retval;
		dst += retval;
		len -= retval;			
	}
	return copied;
}