/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <sys/proc.h>
#include <sys/systm.h>
#include <sys/param.h>
#include <sys/atomic.h>
#include <sys/kmem.h>
#include <sys/sysmacros.h>
#include <sys/procset.h>
#include <sys/corectl.h>
#include <sys/zone.h>
#include <sys/cmn_err.h>
#include <sys/policy.h>
/*
* Core File Settings
* ------------------
*
* A process's core file path and content live in separate reference-counted
* structures. The corectl_content_t structure is fairly straightforward --
* the only subtlety is that we only really _need_ the mutex on architectures
* on which 64-bit memory operations are not atomic. The corectl_path_t
* structure is slightly trickier in that it contains a refstr_t rather than
* just a char * string. This is to allow consumers of the data in that
* structure (the core dumping sub-system for example) to safely use the
* string without holding any locks on it in light of updates.
*
* At system and zone boot, init_core() sets init(1M)'s core file path and
* content to the same value as the fields core_default_path and
* core_default_content respectively (for the global zone). All subsequent
* children of init(1M) reference those same settings. During boot coreadm(1M)
* is invoked with the -u option to update the system settings from
* /etc/coreadm.conf. This has the effect of also changing the values in
* core_default_path and core_default_content which updates the core file
* settings for all processes in the zone. Each zone has different default
* settings; when processes enter a non-global zone, their core file path and
* content are set to the zone's default path and content.
*
* Processes that have their core file settings explicitly overridden using
* coreadm(1M) no longer reference core_default_path or core_default_content
* so subsequent changes to the default will not affect them.
*/
zone_key_t core_zone_key;
static int set_proc_info(pid_t pid, const char *path, core_content_t content);
static corectl_content_t *
corectl_content_alloc(core_content_t cc)
{
corectl_content_t *ccp;
ccp = kmem_zalloc(sizeof (corectl_content_t), KM_SLEEP);
ccp->ccc_content = cc;
ccp->ccc_refcnt = 1;
return (ccp);
}
core_content_t
corectl_content_value(corectl_content_t *ccp)
{
core_content_t content;
mutex_enter(&ccp->ccc_mtx);
content = ccp->ccc_content;
mutex_exit(&ccp->ccc_mtx);
return (content);
}
static void
corectl_content_set(corectl_content_t *ccp, core_content_t content)
{
mutex_enter(&ccp->ccc_mtx);
ccp->ccc_content = content;
mutex_exit(&ccp->ccc_mtx);
}
void
corectl_content_hold(corectl_content_t *ccp)
{
atomic_inc_32(&ccp->ccc_refcnt);
}
void
corectl_content_rele(corectl_content_t *ccp)
{
if (atomic_dec_32_nv(&ccp->ccc_refcnt) == 0)
kmem_free(ccp, sizeof (corectl_content_t));
}
static corectl_path_t *
corectl_path_alloc(const char *path)
{
corectl_path_t *ccp;
ccp = kmem_zalloc(sizeof (corectl_path_t), KM_SLEEP);
ccp->ccp_path = refstr_alloc(path);
ccp->ccp_refcnt = 1;
return (ccp);
}
refstr_t *
corectl_path_value(corectl_path_t *ccp)
{
refstr_t *path;
mutex_enter(&ccp->ccp_mtx);
refstr_hold(path = ccp->ccp_path);
mutex_exit(&ccp->ccp_mtx);
return (path);
}
static void
corectl_path_set(corectl_path_t *ccp, const char *path)
{
refstr_t *npath = refstr_alloc(path);
mutex_enter(&ccp->ccp_mtx);
refstr_rele(ccp->ccp_path);
ccp->ccp_path = npath;
mutex_exit(&ccp->ccp_mtx);
}
void
corectl_path_hold(corectl_path_t *ccp)
{
atomic_inc_32(&ccp->ccp_refcnt);
}
void
corectl_path_rele(corectl_path_t *ccp)
{
if (atomic_dec_32_nv(&ccp->ccp_refcnt) == 0) {
refstr_rele(ccp->ccp_path);
kmem_free(ccp, sizeof (corectl_path_t));
}
}
/*
* Constructor routine to be called when a zone is created.
*/
/*ARGSUSED*/
static void *
core_init_zone(zoneid_t zoneid)
{
struct core_globals *cg;
cg = kmem_alloc(sizeof (*cg), KM_SLEEP);
mutex_init(&cg->core_lock, NULL, MUTEX_DEFAULT, NULL);
cg->core_file = NULL;
cg->core_options = CC_PROCESS_PATH;
cg->core_content = CC_CONTENT_DEFAULT;
cg->core_rlimit = RLIM64_INFINITY;
cg->core_default_path = corectl_path_alloc("core");
cg->core_default_content = corectl_content_alloc(CC_CONTENT_DEFAULT);
return (cg);
}
/*
* Destructor routine to be called when a zone is destroyed.
*/
/*ARGSUSED*/
static void
core_free_zone(zoneid_t zoneid, void *arg)
{
struct core_globals *cg = arg;
if (cg == NULL)
return;
if (cg->core_file != NULL)
refstr_rele(cg->core_file);
corectl_path_rele(cg->core_default_path);
corectl_content_rele(cg->core_default_content);
kmem_free(cg, sizeof (*cg));
}
/*
* Called from start_init_common(), to set init's core file path and content.
*/
void
init_core(void)
{
struct core_globals *cg;
/*
* The first time we hit this, in the global zone, we have to
* initialize the zsd key.
*/
if (INGLOBALZONE(curproc)) {
zone_key_create(&core_zone_key, core_init_zone, NULL,
core_free_zone);
}
/*
* zone_key_create will have called core_init_zone for the
* global zone, which sets up the default path and content
* variables.
*/
VERIFY((cg = zone_getspecific(core_zone_key, curproc->p_zone)) != NULL);
corectl_path_hold(cg->core_default_path);
corectl_content_hold(cg->core_default_content);
curproc->p_corefile = cg->core_default_path;
curproc->p_content = cg->core_default_content;
}
int
corectl(int subcode, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3)
{
int error = 0;
proc_t *p;
refstr_t *rp;
size_t size;
char *path;
core_content_t content = CC_CONTENT_INVALID;
struct core_globals *cg;
zone_t *zone = curproc->p_zone;
cg = zone_getspecific(core_zone_key, zone);
ASSERT(cg != NULL);
switch (subcode) {
case CC_SET_OPTIONS:
if ((error = secpolicy_coreadm(CRED())) == 0) {
if (arg1 & ~CC_OPTIONS)
error = EINVAL;
else
cg->core_options = (uint32_t)arg1;
}
break;
case CC_GET_OPTIONS:
return (cg->core_options);
case CC_GET_GLOBAL_PATH:
case CC_GET_DEFAULT_PATH:
case CC_GET_PROCESS_PATH:
if (subcode == CC_GET_GLOBAL_PATH) {
mutex_enter(&cg->core_lock);
if ((rp = cg->core_file) != NULL)
refstr_hold(rp);
mutex_exit(&cg->core_lock);
} else if (subcode == CC_GET_DEFAULT_PATH) {
rp = corectl_path_value(cg->core_default_path);
} else {
rp = NULL;
mutex_enter(&pidlock);
if ((p = prfind((pid_t)arg3)) == NULL ||
p->p_stat == SIDL) {
mutex_exit(&pidlock);
error = ESRCH;
} else {
mutex_enter(&p->p_lock);
mutex_exit(&pidlock);
mutex_enter(&p->p_crlock);
if (!hasprocperm(p->p_cred, CRED()))
error = EPERM;
else if (p->p_corefile != NULL)
rp = corectl_path_value(p->p_corefile);
mutex_exit(&p->p_crlock);
mutex_exit(&p->p_lock);
}
}
if (rp == NULL) {
if (error == 0 && suword8((void *)arg1, 0))
error = EFAULT;
} else {
error = copyoutstr(refstr_value(rp), (char *)arg1,
(size_t)arg2, NULL);
refstr_rele(rp);
}
break;
case CC_SET_GLOBAL_PATH:
case CC_SET_DEFAULT_PATH:
if ((error = secpolicy_coreadm(CRED())) != 0)
break;
/* FALLTHROUGH */
case CC_SET_PROCESS_PATH:
if ((size = MIN((size_t)arg2, MAXPATHLEN)) == 0) {
error = EINVAL;
break;
}
path = kmem_alloc(size, KM_SLEEP);
error = copyinstr((char *)arg1, path, size, NULL);
if (error == 0) {
if (subcode == CC_SET_PROCESS_PATH) {
error = set_proc_info((pid_t)arg3, path, 0);
} else if (subcode == CC_SET_DEFAULT_PATH) {
corectl_path_set(cg->core_default_path, path);
} else if (*path != '\0' && *path != '/') {
error = EINVAL;
} else {
refstr_t *nrp = refstr_alloc(path);
mutex_enter(&cg->core_lock);
rp = cg->core_file;
if (*path == '\0')
cg->core_file = NULL;
else
refstr_hold(cg->core_file = nrp);
mutex_exit(&cg->core_lock);
if (rp != NULL)
refstr_rele(rp);
refstr_rele(nrp);
}
}
kmem_free(path, size);
break;
case CC_SET_GLOBAL_CONTENT:
case CC_SET_DEFAULT_CONTENT:
if ((error = secpolicy_coreadm(CRED())) != 0)
break;
/* FALLTHROUGH */
case CC_SET_PROCESS_CONTENT:
error = copyin((void *)arg1, &content, sizeof (content));
if (error != 0)
break;
/*
* If any unknown bits are set, don't let this charade
* continue.
*/
if (content & ~CC_CONTENT_ALL) {
error = EINVAL;
break;
}
if (subcode == CC_SET_PROCESS_CONTENT) {
error = set_proc_info((pid_t)arg2, NULL, content);
} else if (subcode == CC_SET_DEFAULT_CONTENT) {
corectl_content_set(cg->core_default_content, content);
} else {
mutex_enter(&cg->core_lock);
cg->core_content = content;
mutex_exit(&cg->core_lock);
}
break;
case CC_GET_GLOBAL_CONTENT:
content = cg->core_content;
error = copyout(&content, (void *)arg1, sizeof (content));
break;
case CC_GET_DEFAULT_CONTENT:
content = corectl_content_value(cg->core_default_content);
error = copyout(&content, (void *)arg1, sizeof (content));
break;
case CC_GET_PROCESS_CONTENT:
mutex_enter(&pidlock);
if ((p = prfind((pid_t)arg2)) == NULL || p->p_stat == SIDL) {
mutex_exit(&pidlock);
error = ESRCH;
break;
}
mutex_enter(&p->p_lock);
mutex_exit(&pidlock);
mutex_enter(&p->p_crlock);
if (!hasprocperm(p->p_cred, CRED()))
error = EPERM;
else if (p->p_content == NULL)
content = CC_CONTENT_NONE;
else
content = corectl_content_value(p->p_content);
mutex_exit(&p->p_crlock);
mutex_exit(&p->p_lock);
if (error == 0)
error = copyout(&content, (void *)arg1,
sizeof (content));
break;
default:
error = EINVAL;
break;
}
if (error)
return (set_errno(error));
return (0);
}
typedef struct {
int cc_count;
corectl_path_t *cc_path;
corectl_content_t *cc_content;
} counter_t;
static int
set_one_proc_info(proc_t *p, counter_t *counterp)
{
corectl_path_t *corefile;
corectl_content_t *content;
mutex_enter(&p->p_crlock);
if (!(p->p_flag & SSYS) && hasprocperm(p->p_cred, CRED())) {
mutex_exit(&p->p_crlock);
counterp->cc_count++;
if (counterp->cc_path != NULL) {
corectl_path_hold(counterp->cc_path);
mutex_enter(&p->p_lock);
corefile = p->p_corefile;
p->p_corefile = counterp->cc_path;
mutex_exit(&p->p_lock);
if (corefile != NULL)
corectl_path_rele(corefile);
} else {
corectl_content_hold(counterp->cc_content);
mutex_enter(&p->p_lock);
content = p->p_content;
p->p_content = counterp->cc_content;
mutex_exit(&p->p_lock);
if (content != NULL)
corectl_content_rele(content);
}
} else {
mutex_exit(&p->p_crlock);
}
return (0);
}
static int
set_proc_info(pid_t pid, const char *path, core_content_t content)
{
proc_t *p;
counter_t counter;
int error = 0;
counter.cc_count = 0;
/*
* Only one of the core file path or content can be set at a time.
*/
if (path != NULL) {
counter.cc_path = corectl_path_alloc(path);
counter.cc_content = NULL;
} else {
counter.cc_path = NULL;
counter.cc_content = corectl_content_alloc(content);
}
if (pid == -1) {
procset_t set;
setprocset(&set, POP_AND, P_ALL, P_MYID, P_ALL, P_MYID);
error = dotoprocs(&set, set_one_proc_info, (char *)&counter);
if (error == 0 && counter.cc_count == 0)
error = EPERM;
} else if (pid > 0) {
mutex_enter(&pidlock);
if ((p = prfind(pid)) == NULL || p->p_stat == SIDL) {
error = ESRCH;
} else {
(void) set_one_proc_info(p, &counter);
if (counter.cc_count == 0)
error = EPERM;
}
mutex_exit(&pidlock);
} else {
int nfound = 0;
pid_t pgid;
if (pid == 0)
pgid = curproc->p_pgrp;
else
pgid = -pid;
mutex_enter(&pidlock);
for (p = pgfind(pgid); p != NULL; p = p->p_pglink) {
if (p->p_stat != SIDL) {
nfound++;
(void) set_one_proc_info(p, &counter);
}
}
mutex_exit(&pidlock);
if (nfound == 0)
error = ESRCH;
else if (counter.cc_count == 0)
error = EPERM;
}
if (path != NULL)
corectl_path_rele(counter.cc_path);
else
corectl_content_rele(counter.cc_content);
if (error)
return (set_errno(error));
return (0);
}
/*
* Give current process the default core settings for its current zone;
* used for processes entering a zone via zone_enter.
*/
void
set_core_defaults(void)
{
proc_t *p = curproc;
struct core_globals *cg;
corectl_path_t *oldpath, *newpath;
corectl_content_t *oldcontent, *newcontent;
cg = zone_getspecific(core_zone_key, p->p_zone);
/* make local copies of default values to protect against change */
newpath = cg->core_default_path;
newcontent = cg->core_default_content;
corectl_path_hold(newpath);
corectl_content_hold(newcontent);
mutex_enter(&p->p_lock);
oldpath = p->p_corefile;
p->p_corefile = newpath;
oldcontent = p->p_content;
p->p_content = newcontent;
mutex_exit(&p->p_lock);
if (oldpath != NULL)
corectl_path_rele(oldpath);
if (oldcontent != NULL)
corectl_content_rele(oldcontent);
}