Add procctl(2) PROC_TRACE_CTL command to enable or disable debugger

attachment to the process.  Note that the command is not intended to
be a security measure, rather it is an obfuscation feature,
implemented for parity with other operating systems.

Discussed with:	jilles, rwatson
Man page fixes by:	rwatson
Sponsored by:	The FreeBSD Foundation
MFC after:	1 week
This commit is contained in:
Konstantin Belousov 2015-01-18 15:13:11 +00:00
parent e3612a4c1f
commit 677258f7e7
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=277322
10 changed files with 185 additions and 4 deletions

View file

@ -29,7 +29,7 @@
.\"
.\" $FreeBSD$
.\"
.Dd December 16, 2014
.Dd December 29, 2014
.Dt PROCCTL 2
.Os
.Sh NAME
@ -275,7 +275,61 @@ delivery failed, e.g. due to the permission problems.
If no such process exist, the
.Fa rk_fpid
field is set to -1.
.It Dv PROC_TRACE_CTL
Enable or disable tracing of the specified process(es), according to the
value of the integer argument.
Tracing includes attachment to the process using
.Xr ptrace 2
and
.Xr ktrace 2 ,
debugging sysctls,
.Xr hwpmc 4 ,
.Xr dtrace 1
and core dumping.
Possible values for the
.Fa data
argument are:
.Bl -tag -width "Dv PROC_TRACE_CTL_DISABLE_EXEC"
.It Dv PROC_TRACE_CTL_ENABLE
Enable tracing, after it was disabled by
.Dv PROC_TRACE_CTL_DISABLE .
Only allowed for self.
.It Dv PROC_TRACE_CTL_DISABLE
Disable tracing for the specified process.
Tracing is re-enabled when the process changes the executing
program with
.Xr execve 2
syscall.
A child inherits the trace settings from the parent on
.Xr fork 2 .
.It Dv PROC_TRACE_CTL_DISABLE_EXEC
Same as
.Dv PROC_TRACE_CTL_DISABLE ,
but the setting persist for the process even after
.Xr execve 2 .
.El
.It Dv PROC_TRACE_STATUS
Returns the current tracing status for the specified process in
the integer variable pointed to by
.Fa data .
If tracing is disabled,
.Fa data
is set to -1.
If tracing is enabled, but no debugger is attached by
.Xr ptrace 2
syscall,
.Fa data
is set to 0.
If a debugger is attached,
.Fa data
is set to the pid of the debugger process.
.El
.Sh NOTES
Disabling tracing on a process should not be considered a security
feature, as it is bypassable both by the kernel and privileged processes,
and via other system mechanisms.
As such, it should not be relied on to reliably protect cryptographic
keying material or other confidential data.
.Sh RETURN VALUES
If an error occurs, a value of -1 is returned and
.Va errno
@ -343,11 +397,34 @@ The
.Dv PROC_REAP_ACQUIRE
request was issued by a process that had already acquired reaper status
and has not yet released it.
.It Bq Er EBUSY
The
.Dv PROC_TRACE_CTL
request was issued for a process already being traced.
.It Bq Er EPERM
The
.Dv PROC_TRACE_CTL
request to re-enable tracing of the process (
.Dv PROC_TRACE_CTL_ENABLE ) ,
or to disable persistence of the
.Dv PROC_TRACE_CTL_DISABLE
on
.Xr execve 2
was issued for a non-current process.
.It Bq Er EINVAL
The value of the integer
.Fa data
parameter for the
.Dv PROC_TRACE_CTL
request is invalid.
.El
.Sh SEE ALSO
.Xr dtrace 1 ,
.Xr kill 2 ,
.Xr ktrace 2 ,
.Xr ptrace 2 ,
.Xr wait 2 ,
.Xr hwpmc 4 ,
.Xr init 8
.Sh HISTORY
The

View file

@ -2969,6 +2969,7 @@ freebsd32_procctl(struct thread *td, struct freebsd32_procctl_args *uap)
switch (uap->com) {
case PROC_SPROTECT:
case PROC_TRACE_CTL:
error = copyin(PTRIN(uap->data), &flags, sizeof(flags));
if (error != 0)
return (error);
@ -2997,6 +2998,9 @@ freebsd32_procctl(struct thread *td, struct freebsd32_procctl_args *uap)
return (error);
data = &x.rk;
break;
case PROC_TRACE_STATUS:
data = &flags;
break;
default:
return (EINVAL);
}
@ -3012,6 +3016,10 @@ freebsd32_procctl(struct thread *td, struct freebsd32_procctl_args *uap)
if (error == 0)
error = error1;
break;
case PROC_TRACE_STATUS:
if (error == 0)
error = copyout(&flags, uap->data, sizeof(flags));
break;
}
return (error);
}

View file

@ -634,6 +634,8 @@ do_execve(td, args, mac_p)
* it that it now has its own resources back
*/
p->p_flag |= P_EXEC;
if ((p->p_flag2 & P2_NOTRACE_EXEC) == 0)
p->p_flag2 &= ~P2_NOTRACE;
if (p->p_flag & P_PPWAIT) {
p->p_flag &= ~(P_PPWAIT | P_PPTRACE);
cv_broadcast(&p->p_pwait);

View file

@ -494,7 +494,7 @@ do_fork(struct thread *td, int flags, struct proc *p2, struct thread *td2,
* Increase reference counts on shared objects.
*/
p2->p_flag = P_INMEM;
p2->p_flag2 = 0;
p2->p_flag2 = p1->p_flag2 & (P2_NOTRACE | P2_NOTRACE_EXEC);
p2->p_swtick = ticks;
if (p1->p_flag & P_PROFIL)
startprofclock(p2);

View file

@ -280,6 +280,62 @@ reap_kill(struct thread *td, struct proc *p, struct procctl_reaper_kill *rk)
return (error);
}
static int
trace_ctl(struct thread *td, struct proc *p, int state)
{
PROC_LOCK_ASSERT(p, MA_OWNED);
/*
* Ktrace changes p_traceflag from or to zero under the
* process lock, so the test does not need to acquire ktrace
* mutex.
*/
if ((p->p_flag & P_TRACED) != 0 || p->p_traceflag != 0)
return (EBUSY);
switch (state) {
case PROC_TRACE_CTL_ENABLE:
if (td->td_proc != p)
return (EPERM);
p->p_flag2 &= ~(P2_NOTRACE | P2_NOTRACE_EXEC);
break;
case PROC_TRACE_CTL_DISABLE_EXEC:
p->p_flag2 |= P2_NOTRACE_EXEC | P2_NOTRACE;
break;
case PROC_TRACE_CTL_DISABLE:
if ((p->p_flag2 & P2_NOTRACE_EXEC) != 0) {
KASSERT((p->p_flag2 & P2_NOTRACE) != 0,
("dandling P2_NOTRACE_EXEC"));
if (td->td_proc != p)
return (EPERM);
p->p_flag2 &= ~P2_NOTRACE_EXEC;
} else {
p->p_flag2 |= P2_NOTRACE;
}
break;
default:
return (EINVAL);
}
return (0);
}
static int
trace_status(struct thread *td, struct proc *p, int *data)
{
if ((p->p_flag2 & P2_NOTRACE) != 0) {
KASSERT((p->p_flag & P_TRACED) == 0,
("%d traced but tracing disabled", p->p_pid));
*data = -1;
} else if ((p->p_flag & P_TRACED) != 0) {
*data = p->p_pptr->p_pid;
} else {
*data = 0;
}
return (0);
}
#ifndef _SYS_SYSPROTO_H_
struct procctl_args {
idtype_t idtype;
@ -302,6 +358,7 @@ sys_procctl(struct thread *td, struct procctl_args *uap)
switch (uap->com) {
case PROC_SPROTECT:
case PROC_TRACE_CTL:
error = copyin(uap->data, &flags, sizeof(flags));
if (error != 0)
return (error);
@ -328,6 +385,9 @@ sys_procctl(struct thread *td, struct procctl_args *uap)
return (error);
data = &x.rk;
break;
case PROC_TRACE_STATUS:
data = &flags;
break;
default:
return (EINVAL);
}
@ -342,6 +402,10 @@ sys_procctl(struct thread *td, struct procctl_args *uap)
if (error == 0)
error = error1;
break;
case PROC_TRACE_STATUS:
if (error == 0)
error = copyout(&flags, uap->data, sizeof(flags));
break;
}
return (error);
}
@ -364,6 +428,10 @@ kern_procctl_single(struct thread *td, struct proc *p, int com, void *data)
return (reap_getpids(td, p, data));
case PROC_REAP_KILL:
return (reap_kill(td, p, data));
case PROC_TRACE_CTL:
return (trace_ctl(td, p, *(int *)data));
case PROC_TRACE_STATUS:
return (trace_status(td, p, data));
default:
return (EINVAL);
}
@ -375,6 +443,7 @@ kern_procctl(struct thread *td, idtype_t idtype, id_t id, int com, void *data)
struct pgrp *pg;
struct proc *p;
int error, first_error, ok;
bool tree_locked;
switch (com) {
case PROC_REAP_ACQUIRE:
@ -382,6 +451,7 @@ kern_procctl(struct thread *td, idtype_t idtype, id_t id, int com, void *data)
case PROC_REAP_STATUS:
case PROC_REAP_GETPIDS:
case PROC_REAP_KILL:
case PROC_TRACE_STATUS:
if (idtype != P_PID)
return (EINVAL);
}
@ -391,11 +461,17 @@ kern_procctl(struct thread *td, idtype_t idtype, id_t id, int com, void *data)
case PROC_REAP_STATUS:
case PROC_REAP_GETPIDS:
case PROC_REAP_KILL:
case PROC_TRACE_CTL:
sx_slock(&proctree_lock);
tree_locked = true;
break;
case PROC_REAP_ACQUIRE:
case PROC_REAP_RELEASE:
sx_xlock(&proctree_lock);
tree_locked = true;
break;
case PROC_TRACE_STATUS:
tree_locked = false;
break;
default:
return (EINVAL);
@ -456,6 +532,7 @@ kern_procctl(struct thread *td, idtype_t idtype, id_t id, int com, void *data)
error = EINVAL;
break;
}
sx_unlock(&proctree_lock);
if (tree_locked)
sx_unlock(&proctree_lock);
return (error);
}

View file

@ -1714,6 +1714,13 @@ p_candebug(struct thread *td, struct proc *p)
if ((p->p_flag & P_INEXEC) != 0)
return (EBUSY);
/* Denied explicitely */
if ((p->p_flag2 & P2_NOTRACE) != 0) {
error = priv_check(td, PRIV_DEBUG_DENIED);
if (error != 0)
return (error);
}
return (0);
}

View file

@ -3247,7 +3247,8 @@ coredump(struct thread *td)
MPASS((p->p_flag & P_HADTHREADS) == 0 || p->p_singlethread == td);
_STOPEVENT(p, S_CORE, 0);
if (!do_coredump || (!sugid_coredump && (p->p_flag & P_SUGID) != 0)) {
if (!do_coredump || (!sugid_coredump && (p->p_flag & P_SUGID) != 0) ||
(p->p_flag2 & P2_NOTRACE) != 0) {
PROC_UNLOCK(p);
return (EFAULT);
}

View file

@ -111,6 +111,7 @@
#define PRIV_DEBUG_DIFFCRED 80 /* Exempt debugging other users. */
#define PRIV_DEBUG_SUGID 81 /* Exempt debugging setuid proc. */
#define PRIV_DEBUG_UNPRIV 82 /* Exempt unprivileged debug limit. */
#define PRIV_DEBUG_DENIED 83 /* Exempt P2_NOTRACE. */
/*
* Dtrace privileges.

View file

@ -675,6 +675,8 @@ struct proc {
/* These flags are kept in p_flag2. */
#define P2_INHERIT_PROTECTED 0x00000001 /* New children get P_PROTECTED. */
#define P2_NOTRACE 0x00000002 /* No ptrace(2) attach or coredumps. */
#define P2_NOTRACE_EXEC 0x00000004 /* Keep P2_NOPTRACE on exec(2). */
/* Flags protected by proctree_lock, kept in p_treeflags. */
#define P_TREE_ORPHANED 0x00000001 /* Reparented, on orphan list */

View file

@ -41,6 +41,8 @@
#define PROC_REAP_STATUS 4 /* reaping status */
#define PROC_REAP_GETPIDS 5 /* get descendants */
#define PROC_REAP_KILL 6 /* kill descendants */
#define PROC_TRACE_CTL 7 /* en/dis ptrace and coredumps */
#define PROC_TRACE_STATUS 8 /* query tracing status */
/* Operations for PROC_SPROTECT (passed in integer arg). */
#define PPROT_OP(x) ((x) & 0xf)
@ -96,6 +98,10 @@ struct procctl_reaper_kill {
#define REAPER_KILL_CHILDREN 0x00000001
#define REAPER_KILL_SUBTREE 0x00000002
#define PROC_TRACE_CTL_ENABLE 1
#define PROC_TRACE_CTL_DISABLE 2
#define PROC_TRACE_CTL_DISABLE_EXEC 3
#ifndef _KERNEL
__BEGIN_DECLS
int procctl(idtype_t, id_t, int, void *);