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:
parent
e3612a4c1f
commit
677258f7e7
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -634,6 +634,8 @@ interpret:
|
||||
* 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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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 */
|
||||
|
@ -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 *);
|
||||
|
Loading…
x
Reference in New Issue
Block a user