1/*
2 * Copyright (c) 2012 The Chromium OS Authors.
3 *
4 * Based on:
5 * http://bazaar.launchpad.net/~ubuntu-bugcontrol/qa-regression-testing/master/view/head:/scripts/kernel-security/ptrace/thread-prctl.c
6 * Copyright 2011 Canonical, Ltd
7 * License: GPLv3
8 * Author: Kees Cook <kees.cook@canonical.com>
9 *
10 * Based on reproducer written by Philippe Waroquiers in:
11 * https://launchpad.net/bugs/729839
12 */
13#include <unistd.h>
14#include <stdio.h>
15#include <stdlib.h>
16#include <sys/types.h>
17#include <sys/wait.h>
18#include <signal.h>
19#include <string.h>
20#include <pthread.h>
21#include <sys/ptrace.h>
22#include <sys/prctl.h>
23#ifndef PR_SET_PTRACER
24# define PR_SET_PTRACER 0x59616d61
25#endif
26
27int tracee_method = 0;
28#define TRACEE_FORKS_FROM_TRACER        0
29#define TRACEE_CALLS_PRCTL_FROM_MAIN    1
30#define TRACEE_CALLS_PRCTL_FROM_THREAD  2
31
32/* Define some distinct exit values to aid failure debugging. */
33#define EXIT_FORK_TRACEE             1
34#define EXIT_FORK_TRACER             2
35#define EXIT_PIPE_COMMUNICATION      3
36#define EXIT_PIPE_NOTIFICATION       4
37#define EXIT_TRACEE_PIPE_READ        5
38#define EXIT_TRACEE_UNREACHABLE      6
39#define EXIT_TRACER_PIPE_READ        7
40#define EXIT_TRACER_PTRACE_ATTACH    8
41#define EXIT_TRACER_PTRACE_CONTINUE  9
42#define EXIT_TRACER_UNREACHABLE     10
43
44int main_does_ptrace = 0;
45
46int ret;
47int pipes[2];
48int notification[2];
49pid_t tracer, tracee;
50
51static void *thr_fn(void *v)
52{
53    printf("tracee thread started\n");
54    if (tracee_method == TRACEE_CALLS_PRCTL_FROM_THREAD) {
55        ret = prctl (PR_SET_PTRACER, tracer, 0, 0, 0);
56        printf("tracee thread prtctl result: %d\n", ret);
57    }
58    printf("tracee thread finishing\n");
59    return NULL;
60}
61
62void start_tracee(void);
63
64void * tracer_main(void * data)
65{
66    long ptrace_result;
67    char buf[8];
68    int saw;
69
70    tracer = getpid();
71    printf("tracer %d waiting\n", tracer);
72
73    if (tracee_method == TRACEE_FORKS_FROM_TRACER) {
74        printf("forking tracee from tracer\n");
75        start_tracee();
76    }
77
78    close(pipes[1]);
79    close(notification[0]);
80    close(notification[1]);
81
82    saw = read(pipes[0], buf, 3);
83    if (saw < 3) {
84        perror("tracer pipe read");
85        exit(EXIT_TRACER_PIPE_READ);
86    }
87
88    printf("tracer to PTRACE_ATTACH my tracee %d\n", tracee);
89    ptrace_result = ptrace(PTRACE_ATTACH, tracee, NULL, NULL);
90    if (ptrace_result != 0) {
91        fflush(NULL);
92        perror ("tracer ptrace attach has failed");
93        exit(EXIT_TRACER_PTRACE_ATTACH);
94    }
95    printf ("tracer ptrace attach successful\n");
96
97    /* Wait for signal. */
98    printf("tracer waiting for tracee to SIGSTOP\n");
99    waitpid(tracee, NULL, 0);
100
101    printf("tracer to PTRACE_CONT tracee\n");
102    ptrace_result = ptrace(PTRACE_CONT, tracee, NULL, NULL);
103    if (ptrace_result != 0) {
104        fflush(NULL);
105        perror ("tracer ptrace continue has failed");
106        exit(EXIT_TRACER_PTRACE_CONTINUE);
107    }
108    printf ("tracer ptrace continue successful\n");
109
110    printf("tracer returning 0\n");
111    fflush(NULL);
112    exit(EXIT_SUCCESS);
113
114    return NULL;
115}
116
117/* Tracee knows nothing, needs tracee and tracer pid. */
118void tracee_main(void) {
119    char buf[1024];
120    int saw;
121    pthread_t thr;
122
123    tracee = getpid();
124    close(pipes[0]);
125
126    printf("tracee %d reading tracer pid\n", tracee);
127    close(notification[1]);
128    saw = read(notification[0], buf, 1024);
129    if (saw < 1) {
130        perror("pipe read");
131        exit(EXIT_TRACEE_PIPE_READ);
132    }
133    buf[saw]='\0';
134    tracer = atoi(buf);
135
136    printf("tracee %d started (expecting %d as tracer)\n", tracee, tracer);
137
138    /* Handle setting PR_SET_PTRACER. */
139    switch (tracee_method) {
140        case TRACEE_CALLS_PRCTL_FROM_MAIN:
141            ret = prctl (PR_SET_PTRACER, tracer, 0, 0, 0);
142            printf("tracee main prtctl result: %d \n", ret);
143            break;
144        case TRACEE_CALLS_PRCTL_FROM_THREAD:
145            printf("tracee thread starting\n");
146            pthread_create(&thr, NULL, thr_fn, NULL);
147            pthread_join(thr, NULL);
148            printf("tracee thread finished\n");
149            break;
150        default:
151            break;
152    }
153
154    /* Wait for Oedipal action. */
155    printf("tracee triggering tracer\n");
156    fflush(NULL);
157    write(pipes[1], "ok\n", 3);
158
159    printf("tracee waiting for master\n");
160    saw = read(notification[0], buf, 1024);
161    buf[saw] = '\0';
162
163    printf("tracee finished (%s)\n", buf);
164    exit(EXIT_SUCCESS);
165}
166
167void start_tracee(void)
168{
169    fflush(NULL);
170    tracee = fork();
171    if (tracee < 0) {
172        perror("fork tracee");
173        exit(EXIT_FORK_TRACEE);
174    }
175    if (tracee == 0) {
176        tracee_main();
177        exit(EXIT_TRACEE_UNREACHABLE);
178    }
179}
180
181/* Tracer knows tracee, needs tracer pid. */
182int main(int argc, char*argv[])
183{
184    int status;
185    char buf[1024];
186
187    if (argc > 1) {
188        /* Operational states:
189         * 0: tracer forks tracee.
190         * 1: tracee calls prctl from main process.
191         * 2: tracee calls prctl from non-leader thread.
192         */
193        tracee_method = atoi(argv[1]);
194    }
195    if (argc > 2) {
196        /* Operational states:
197         * 0: ptrace happens from non-leader thread.
198         * 1: ptrace happens from main process.
199         */
200        main_does_ptrace = atoi(argv[2]) != 0;
201    }
202
203    if (tracee_method != TRACEE_FORKS_FROM_TRACER) {
204        printf("will issue prctl from %s\n",
205               tracee_method == TRACEE_CALLS_PRCTL_FROM_MAIN ?
206                    "main" : "thread");
207    }
208    else {
209        printf("will fork tracee from tracer\n");
210    }
211    printf("will issue ptrace from tracer %s\n",
212           main_does_ptrace ? "main" : "thread");
213
214    printf("master is %d\n", getpid());
215
216    if (pipe(notification)<0) {
217        perror("pipe");
218        exit(EXIT_PIPE_NOTIFICATION);
219    }
220    if (pipe(pipes)<0) {
221        perror("pipe");
222        exit(EXIT_PIPE_COMMUNICATION);
223    }
224
225    if (tracee_method != TRACEE_FORKS_FROM_TRACER) {
226        printf("forking tracee from master\n");
227        start_tracee();
228    }
229
230    fflush(NULL);
231    tracer = fork();
232    if (tracer < 0) {
233        perror("fork tracer");
234        exit(EXIT_FORK_TRACER);
235    }
236    if (tracer == 0) {
237        printf("tracer is %d\n", getpid());
238        if (main_does_ptrace) {
239            tracer_main(NULL);
240        }
241        else {
242            pthread_t thread;
243            pthread_create(&thread, NULL, tracer_main, NULL);
244            pthread_join(thread, NULL);
245        }
246        exit(EXIT_TRACER_UNREACHABLE);
247    }
248
249    /* Leave the pipes for the tracee and tracer. */
250    close(pipes[0]);
251    close(pipes[1]);
252
253    /* Close our end of pid notification. */
254    close(notification[0]);
255    sprintf(buf, "%d", tracer);
256    write(notification[1], buf, strlen(buf));
257
258    printf("master waiting for tracer to finish\n");
259    fflush(NULL);
260    waitpid(tracer, &status, 0);
261
262    printf("master waiting for tracee to finish\n");
263    fflush(NULL);
264    write(notification[1], "stop", 4);
265    kill(tracee, SIGCONT); // Just in case.
266    waitpid(tracee, NULL, 0);
267
268    status = WEXITSTATUS(status);
269    printf("master saw rc %d from tracer\n", status);
270    return status;
271}
272
273