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

10
#include <linux/module.h>
Linus Torvalds's avatar
Linus Torvalds committed
11 12 13 14
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/highmem.h>
15
#include <linux/pagemap.h>
Linus Torvalds's avatar
Linus Torvalds committed
16
#include <linux/smp_lock.h>
17
#include <linux/ptrace.h>
18
#include <linux/security.h>
Linus Torvalds's avatar
Linus Torvalds committed
19 20 21 22

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

23 24 25 26 27 28 29 30 31 32 33
/*
 * ptrace a task: make the debugger its new parent and
 * move it to the ptrace list.
 *
 * Must be called with the tasklist lock write-held.
 */
void __ptrace_link(task_t *child, task_t *new_parent)
{
	if (!list_empty(&child->ptrace_list))
		BUG();
	if (child->parent == new_parent)
Ingo Molnar's avatar
Ingo Molnar committed
34
		return;
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
	list_add(&child->ptrace_list, &child->parent->ptrace_children);
	REMOVE_LINKS(child);
	child->parent = new_parent;
	SET_LINKS(child);
}
 
/*
 * unptrace a task: move it back to its original parent and
 * remove it from the ptrace list.
 *
 * Must be called with the tasklist lock write-held.
 */
void __ptrace_unlink(task_t *child)
{
	if (!child->ptrace)
		BUG();
	child->ptrace = 0;
	if (list_empty(&child->ptrace_list))
		return;
	list_del_init(&child->ptrace_list);
	REMOVE_LINKS(child);
	child->parent = child->real_parent;
	SET_LINKS(child);
58 59 60 61 62 63 64 65 66

	if (child->state == TASK_TRACED) {
		/*
		 * Turn a tracing stop into a normal stop now,
		 * since with no tracer there would be no way
		 * to wake it up with SIGCONT or SIGKILL.
		 */
		child->state = TASK_STOPPED;
	}
67 68
}

Linus Torvalds's avatar
Linus Torvalds committed
69 70 71 72 73
/*
 * Check that we have indeed attached to the thing..
 */
int ptrace_check_attach(struct task_struct *child, int kill)
{
74
	int ret = -ESRCH;
Linus Torvalds's avatar
Linus Torvalds committed
75

76 77 78 79 80 81 82 83
	/*
	 * We take the read lock around doing both checks to close a
	 * possible race where someone else was tracing our child and
	 * detached between these two checks.  After this locked check,
	 * we are sure that this is our traced child and that can only
	 * be changed by us so it's not changing right after this.
	 */
	read_lock(&tasklist_lock);
84
	if ((child->ptrace & PT_PTRACED) && child->parent == current &&
85 86
	    (!(child->ptrace & PT_ATTACHED) || child->real_parent != current)
	    && child->signal != NULL) {
87
		ret = 0;
88 89 90 91 92 93 94 95
		spin_lock_irq(&child->sighand->siglock);
		if (child->state == TASK_STOPPED) {
			child->state = TASK_TRACED;
		} else if (child->state != TASK_TRACED && !kill) {
			ret = -ESRCH;
		}
		spin_unlock_irq(&child->sighand->siglock);
	}
96
	read_unlock(&tasklist_lock);
Linus Torvalds's avatar
Linus Torvalds committed
97

98
	if (!ret && !kill) {
Linus Torvalds's avatar
Linus Torvalds committed
99
		wait_task_inactive(child);
Linus Torvalds's avatar
Linus Torvalds committed
100 101 102
	}

	/* All systems go.. */
103
	return ret;
Linus Torvalds's avatar
Linus Torvalds committed
104 105
}

Linus Torvalds's avatar
Linus Torvalds committed
106 107
int ptrace_attach(struct task_struct *task)
{
108
	int retval;
Linus Torvalds's avatar
Linus Torvalds committed
109
	task_lock(task);
110
	retval = -EPERM;
Linus Torvalds's avatar
Linus Torvalds committed
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
	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) ||
 	    (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;
130 131
	retval = security_ptrace(current, task);
	if (retval)
132
		goto bad;
Linus Torvalds's avatar
Linus Torvalds committed
133 134

	/* Go */
Roland McGrath's avatar
Roland McGrath committed
135 136
	task->ptrace |= PT_PTRACED | ((task->real_parent != current)
				      ? PT_ATTACHED : 0);
Linus Torvalds's avatar
Linus Torvalds committed
137 138
	if (capable(CAP_SYS_PTRACE))
		task->ptrace |= PT_PTRACE_CAP;
Linus Torvalds's avatar
Linus Torvalds committed
139 140 141
	task_unlock(task);

	write_lock_irq(&tasklist_lock);
142
	__ptrace_link(task, current);
Linus Torvalds's avatar
Linus Torvalds committed
143 144
	write_unlock_irq(&tasklist_lock);

Ingo Molnar's avatar
Ingo Molnar committed
145
	force_sig_specific(SIGSTOP, task);
Linus Torvalds's avatar
Linus Torvalds committed
146 147 148 149
	return 0;

bad:
	task_unlock(task);
150
	return retval;
Linus Torvalds's avatar
Linus Torvalds committed
151 152
}

Linus Torvalds's avatar
Linus Torvalds committed
153 154 155 156 157 158 159 160 161 162
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->exit_code = data;
163

Linus Torvalds's avatar
Linus Torvalds committed
164
	write_lock_irq(&tasklist_lock);
165 166
	__ptrace_unlink(child);
	/* .. and wake it up. */
167
	if (child->exit_state != EXIT_ZOMBIE)
168
		wake_up_process(child);
Linus Torvalds's avatar
Linus Torvalds committed
169 170 171 172
	write_unlock_irq(&tasklist_lock);

	return 0;
}
Linus Torvalds's avatar
Linus Torvalds committed
173

Linus Torvalds's avatar
Linus Torvalds committed
174
/*
Linus Torvalds's avatar
Linus Torvalds committed
175 176 177
 * 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
178 179 180 181 182
 */

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
183 184 185
	struct vm_area_struct *vma;
	struct page *page;
	void *old_buf = buf;
Linus Torvalds's avatar
Linus Torvalds committed
186

187
	mm = get_task_mm(tsk);
Linus Torvalds's avatar
Linus Torvalds committed
188 189 190
	if (!mm)
		return 0;

Linus Torvalds's avatar
Linus Torvalds committed
191
	down_read(&mm->mmap_sem);
Linus Torvalds's avatar
Linus Torvalds committed
192 193 194 195 196
	/* ignore errors, just check how much was sucessfully transfered */
	while (len) {
		int bytes, ret, offset;
		void *maddr;

197
		ret = get_user_pages(tsk, mm, addr, 1,
Linus Torvalds's avatar
Linus Torvalds committed
198 199 200
				write, 1, &page, &vma);
		if (ret <= 0)
			break;
Linus Torvalds's avatar
Linus Torvalds committed
201

Linus Torvalds's avatar
Linus Torvalds committed
202 203 204 205 206 207 208
		bytes = len;
		offset = addr & (PAGE_SIZE-1);
		if (bytes > PAGE_SIZE-offset)
			bytes = PAGE_SIZE-offset;

		maddr = kmap(page);
		if (write) {
209 210
			copy_to_user_page(vma, page, addr,
					  maddr + offset, buf, bytes);
211
			set_page_dirty_lock(page);
Linus Torvalds's avatar
Linus Torvalds committed
212
		} else {
213 214
			copy_from_user_page(vma, page, addr,
					    buf, maddr + offset, bytes);
Linus Torvalds's avatar
Linus Torvalds committed
215 216
		}
		kunmap(page);
217
		page_cache_release(page);
Linus Torvalds's avatar
Linus Torvalds committed
218 219
		len -= bytes;
		buf += bytes;
Dave Jones's avatar
Dave Jones committed
220
		addr += bytes;
Linus Torvalds's avatar
Linus Torvalds committed
221
	}
Linus Torvalds's avatar
Linus Torvalds committed
222
	up_read(&mm->mmap_sem);
Linus Torvalds's avatar
Linus Torvalds committed
223
	mmput(mm);
Linus Torvalds's avatar
Linus Torvalds committed
224 225
	
	return buf - old_buf;
Linus Torvalds's avatar
Linus Torvalds committed
226 227
}

228
int ptrace_readdata(struct task_struct *tsk, unsigned long src, char __user *dst, int len)
Linus Torvalds's avatar
Linus Torvalds committed
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
{
	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;
}

253
int ptrace_writedata(struct task_struct *tsk, char __user *src, unsigned long dst, int len)
Linus Torvalds's avatar
Linus Torvalds committed
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
{
	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;
}
277

278
static int ptrace_setoptions(struct task_struct *child, long data)
279
{
280 281
	child->ptrace &= ~PT_TRACE_MASK;

282 283 284
	if (data & PTRACE_O_TRACESYSGOOD)
		child->ptrace |= PT_TRACESYSGOOD;

285 286 287 288 289 290 291 292 293 294 295 296
	if (data & PTRACE_O_TRACEFORK)
		child->ptrace |= PT_TRACE_FORK;

	if (data & PTRACE_O_TRACEVFORK)
		child->ptrace |= PT_TRACE_VFORK;

	if (data & PTRACE_O_TRACECLONE)
		child->ptrace |= PT_TRACE_CLONE;

	if (data & PTRACE_O_TRACEEXEC)
		child->ptrace |= PT_TRACE_EXEC;

297 298 299 300 301
	if (data & PTRACE_O_TRACEVFORKDONE)
		child->ptrace |= PT_TRACE_VFORK_DONE;

	if (data & PTRACE_O_TRACEEXIT)
		child->ptrace |= PT_TRACE_EXIT;
302

303
	return (data & ~PTRACE_O_MASK) ? -EINVAL : 0;
304 305
}

306
static int ptrace_getsiginfo(struct task_struct *child, siginfo_t __user * data)
307
{
308 309
	if (child->last_siginfo == NULL)
		return -EINVAL;
310
	return copy_siginfo_to_user(data, child->last_siginfo);
311 312
}

313
static int ptrace_setsiginfo(struct task_struct *child, siginfo_t __user * data)
314
{
315 316
	if (child->last_siginfo == NULL)
		return -EINVAL;
317
	if (copy_from_user(child->last_siginfo, data, sizeof (siginfo_t)) != 0)
318 319 320 321
		return -EFAULT;
	return 0;
}

322 323 324 325 326 327 328 329 330 331 332 333
int ptrace_request(struct task_struct *child, long request,
		   long addr, long data)
{
	int ret = -EIO;

	switch (request) {
#ifdef PTRACE_OLDSETOPTIONS
	case PTRACE_OLDSETOPTIONS:
#endif
	case PTRACE_SETOPTIONS:
		ret = ptrace_setoptions(child, data);
		break;
334
	case PTRACE_GETEVENTMSG:
335
		ret = put_user(child->ptrace_message, (unsigned long __user *) data);
336
		break;
337
	case PTRACE_GETSIGINFO:
338
		ret = ptrace_getsiginfo(child, (siginfo_t __user *) data);
339 340
		break;
	case PTRACE_SETSIGINFO:
341
		ret = ptrace_setsiginfo(child, (siginfo_t __user *) data);
342
		break;
343 344 345 346 347 348
	default:
		break;
	}

	return ret;
}