cdrtools/cdrecord/priv.c

498 lines
13 KiB
C
Raw Permalink Normal View History

2025-06-15 04:19:58 +08:00
/* @(#)priv.c 1.4 13/10/12 Copyright 2006-2013 J. Schilling */
#include <schily/mconfig.h>
#ifndef lint
static UConst char sccsid[] =
"@(#)priv.c 1.4 13/10/12 Copyright 2006-2013 J. Schilling";
#endif
/*
* Cdrecord support functions to support fine grained privileges.
*
* Copyright (c) 2006-2013 J. Schilling
*/
/*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* See the file CDDL.Schily.txt in this distribution for details.
* A copy of the CDDL is also available via the Internet at
* http://www.opensource.org/licenses/cddl1.txt
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file CDDL.Schily.txt from this distribution.
*/
#include <schily/mconfig.h>
#include <schily/standard.h>
#include <schily/stdlib.h>
#include <schily/string.h>
#include <schily/schily.h>
#include <schily/priv.h>
#ifdef HAVE_LINUX_CAPS
#define NCAPS (sizeof (caplist) / sizeof (caplist[0]))
#endif
#ifdef CDDA2WAV
EXPORT void priv_init __PR((void));
EXPORT void priv_on __PR((void));
EXPORT void priv_off __PR((void));
#endif
#if defined(CDRECORD) || defined(READCD)
EXPORT void priv_drop __PR((void));
EXPORT BOOL priv_from_priv __PR((void));
#endif
EXPORT BOOL priv_eff_priv __PR((int pname));
#ifdef HAVE_SOLARIS_PPRIV
LOCAL BOOL priv_eff __PR((const char *pname));
#endif
#ifdef HAVE_LINUX_CAPS
LOCAL BOOL priv_eff __PR((int pname));
#endif
#ifdef HAVE_SOLARIS_PPRIV
EXPORT void do_pfexec __PR((int ac, char *av[], ...));
#endif
/*
* Solaris uses the advanced privileges(5) mechanism for fine grained
* prilileges.
*
* The following Solaris privileges are needed by cdrecord:
*
* PRIV_FILE_DAC_READ only to open /dev/ nodes for USCSICMD
* PRIV_FILE_DAC_WRITE in addition to open /dev/scg* for SCGIO_CMD
* PRIV_SYS_DEVICES during whole runtime: for USCSICMD
* PRIV_PROC_LOCK_MEMORY only to call mlockall()
* PRIV_PROC_PRIOCNTL only to call priocntl()
* PRIV_NET_PRIVADDR only to create privileged socket (remote SCSI)
*
*
* The POSIX.1e draft has been withdrawn in 1997.
* Linux started to implement this outdated concept in 1997.
*
* The following Linux capabilities are needed by cdrecord:
*
* CAP_DAC_OVERRIDE only to open /dev/ nodes
* CAP_NET_BIND_SERVICE only to create privileged socket (remote SCSI)
* CAP_IPC_LOCK only to call mlockall()
* CAP_SYS_RAWIO during whole runtime: for SCSI commands
* CAP_SYS_ADMIN during whole runtime: for SCSI commands
* CAP_SYS_NICE only to call sched_*() and setpriority()
* CAP_SYS_RESOURCE only to support mlockall()
*
* Use:
* setcap cap_sys_resource,cap_dac_override,cap_sys_admin,cap_sys_nice,cap_net_bind_service,cap_ipc_lock,cap_sys_rawio+ep cdrecord
* to set the capabilities for the executable file.
*/
#ifdef CDDA2WAV
/*
* Initial removal of privileges:
* - remove all inheritable additional privileges
* - Remove all effective privileges that are not needed all the time
*/
EXPORT void
priv_init()
{
#ifdef HAVE_PRIV_SET
/*
* Give up privs we do not need anymore.
* We no longer need:
* file_dac_read,sys_devices,proc_priocntl,net_privaddr
*/
priv_set(PRIV_OFF, PRIV_EFFECTIVE,
PRIV_FILE_DAC_READ, PRIV_PROC_PRIOCNTL,
PRIV_NET_PRIVADDR, NULL);
priv_set(PRIV_OFF, PRIV_INHERITABLE,
PRIV_FILE_DAC_READ, PRIV_PROC_PRIOCNTL,
PRIV_NET_PRIVADDR, PRIV_SYS_DEVICES, NULL);
#else
#ifdef HAVE_LINUX_CAPS
/*
* Give up privs we do not need for the moment.
* We no longer need:
* cap_dac_override,cap_net_bind_service,cap_sys_nice
*/
cap_t cset;
cap_value_t caplist[] = {
CAP_DAC_OVERRIDE,
CAP_NET_BIND_SERVICE,
CAP_SYS_NICE,
CAP_SYS_RAWIO, /* Keep as CAP_EFFECTIVE */
CAP_SYS_ADMIN /* Keep as CAP_EFFECTIVE */
};
cset = cap_get_proc();
cap_set_flag(cset, CAP_EFFECTIVE, NCAPS-2, caplist, CAP_CLEAR);
cap_set_flag(cset, CAP_INHERITABLE, NCAPS, caplist, CAP_CLEAR);
if (cap_set_proc(cset) < 0)
errmsg("Cannot set initial process capabilities.\n");
#endif /* HAVE_LINUX_CAPS */
#endif /* HAVE_PRIV_SET */
}
/*
* Get back those effective privileges that are needed for parts of the time
*/
EXPORT void
priv_on()
{
#ifdef HAVE_PRIV_SET
/*
* Get back privs we may need now.
* We need:
* file_dac_read,sys_devices,proc_priocntl,net_privaddr
*/
priv_set(PRIV_ON, PRIV_EFFECTIVE,
PRIV_FILE_DAC_READ, PRIV_PROC_PRIOCNTL,
PRIV_NET_PRIVADDR, NULL);
#else
#ifdef HAVE_LINUX_CAPS
/*
* Get back privs we may need now.
* We need:
* cap_dac_override,cap_net_bind_service,cap_sys_nice
*/
cap_t cset;
cap_value_t caplist[] = {
CAP_DAC_OVERRIDE,
CAP_NET_BIND_SERVICE,
CAP_SYS_NICE
};
cset = cap_get_proc();
cap_set_flag(cset, CAP_EFFECTIVE, NCAPS, caplist, CAP_SET);
if (cap_set_proc(cset) < 0)
errmsg("Cannot regain process capabilities.\n");
#endif /* HAVE_LINUX_CAPS */
#endif /* HAVE_PRIV_SET */
}
/*
* Get rid of those effective privileges that are needed for parts of the time
*/
EXPORT void
priv_off()
{
#ifdef HAVE_PRIV_SET
/*
* Give up privs we do not need at the moment.
* We no longer need:
* file_dac_read,sys_devices,proc_priocntl,net_privaddr
*/
priv_set(PRIV_OFF, PRIV_EFFECTIVE,
PRIV_FILE_DAC_READ, PRIV_PROC_PRIOCNTL,
PRIV_NET_PRIVADDR, NULL);
#else
#ifdef HAVE_LINUX_CAPS
/*
* Give up privs we do not need anymore.
* We no longer need:
* cap_dac_override,cap_net_bind_service,cap_sys_nice
*/
cap_t cset;
cap_value_t caplist[] = {
CAP_DAC_OVERRIDE,
CAP_NET_BIND_SERVICE,
CAP_SYS_NICE
};
cset = cap_get_proc();
cap_set_flag(cset, CAP_EFFECTIVE, NCAPS, caplist, CAP_CLEAR);
if (cap_set_proc(cset) < 0)
errmsg("Cannot deactivate process capabilities.\n");
#endif /* HAVE_LINUX_CAPS */
#endif /* HAVE_PRIV_SET */
}
#endif /* CDDA2WAV */
#if defined(CDRECORD) || defined(READCD)
/*
* Drop all privileges that are no longer needed after startup
*/
EXPORT void
priv_drop()
{
#ifdef HAVE_PRIV_SET
#ifdef PRIVILEGES_DEBUG /* PRIV_DEBUG is defined in <sys/priv.h> */
error("file_dac_read: %d\n", priv_ineffect(PRIV_FILE_DAC_READ));
#endif
/*
* Give up privs we do not need anymore.
* We no longer need:
* file_dac_read,proc_lock_memory,proc_priocntl,net_privaddr
* We still need:
* sys_devices
*/
priv_set(PRIV_OFF, PRIV_EFFECTIVE,
PRIV_FILE_DAC_READ, PRIV_PROC_LOCK_MEMORY,
PRIV_PROC_PRIOCNTL, PRIV_NET_PRIVADDR, NULL);
priv_set(PRIV_OFF, PRIV_PERMITTED,
PRIV_FILE_DAC_READ, PRIV_PROC_LOCK_MEMORY,
PRIV_PROC_PRIOCNTL, PRIV_NET_PRIVADDR, NULL);
priv_set(PRIV_OFF, PRIV_INHERITABLE,
PRIV_FILE_DAC_READ, PRIV_PROC_LOCK_MEMORY,
PRIV_PROC_PRIOCNTL, PRIV_NET_PRIVADDR, PRIV_SYS_DEVICES, NULL);
#else
#ifdef HAVE_LINUX_CAPS
/*
* Give up privs we do not need anymore.
* We no longer need:
* cap_dac_override,cap_net_bind_service,cap_ipc_lock,cap_sys_nice,cap_sys_resource
* We still need:
* cap_sys_rawio,cap_sys_admin
*/
cap_t cset;
cap_value_t caplist[] = {
CAP_DAC_OVERRIDE,
CAP_NET_BIND_SERVICE,
CAP_IPC_LOCK,
CAP_SYS_NICE,
CAP_SYS_RESOURCE
};
cset = cap_get_proc();
cap_set_flag(cset, CAP_EFFECTIVE, NCAPS, caplist, CAP_CLEAR);
cap_set_flag(cset, CAP_INHERITABLE, NCAPS, caplist, CAP_CLEAR);
cap_set_flag(cset, CAP_PERMITTED, NCAPS, caplist, CAP_CLEAR);
if (cap_set_proc(cset) < 0)
errmsg("Cannot drop process capabilities.\n");
#endif /* HAVE_LINUX_CAPS */
#endif /* HAVE_PRIV_SET */
}
/*
* Return TRUE if we have privileges that are not from a suid-root operation.
*/
EXPORT BOOL
priv_from_priv()
{
#ifdef HAVE_PRIV_SET
return (priv_ineffect(PRIV_FILE_DAC_READ) &&
!priv_ineffect(PRIV_PROC_SETID));
#else
#ifdef HAVE_LINUX_CAPS
cap_t cset;
cap_flag_value_t val = CAP_CLEAR;
cset = cap_get_proc();
cap_get_flag(cset, CAP_DAC_OVERRIDE, CAP_EFFECTIVE, &val);
if (val == CAP_CLEAR)
return (FALSE);
val = CAP_SET;
cap_get_flag(cset, CAP_SETUID, CAP_EFFECTIVE, &val);
if (val == CAP_SET)
return (FALSE);
return (TRUE);
#else /* HAVE_LINUX_CAPS */
return (FALSE);
#endif /* HAVE_LINUX_CAPS */
#endif /* HAVE_PRIV_SET */
}
#endif /* defined(CDRECORD) || defined(READCD) */
/*
* An attempt to implement an abstraction layer to detect fine grained
* privileges. This is not implemented in an efficient way (there are multiple
* syscalls to fetch the privileges from the kernel) but a few milliseconds
* should not count.
*/
EXPORT BOOL
priv_eff_priv(pname)
int pname;
{
#if !defined(HAVE_SETEUID) || !defined(HAVE_GETEUID)
return (TRUE);
#else
#ifdef HAVE_SOLARIS_PPRIV
#define DID_PRIV
switch (pname) {
case SCHILY_PRIV_FILE_DAC_READ:
return (priv_eff(PRIV_FILE_DAC_READ));
case SCHILY_PRIV_FILE_DAC_WRITE:
return (priv_eff(PRIV_FILE_DAC_WRITE));
case SCHILY_PRIV_SYS_DEVICES:
return (priv_eff(PRIV_SYS_DEVICES));
case SCHILY_PRIV_PROC_LOCK_MEMORY:
return (priv_eff(PRIV_PROC_LOCK_MEMORY));
case SCHILY_PRIV_PROC_PRIOCNTL:
return (priv_eff(PRIV_PROC_PRIOCNTL));
case SCHILY_PRIV_NET_PRIVADDR:
return (priv_eff(PRIV_NET_PRIVADDR));
}
#endif
#ifdef HAVE_LINUX_CAPS
#define DID_PRIV
switch (pname) {
case SCHILY_PRIV_FILE_DAC_READ:
return (priv_eff(CAP_DAC_READ_SEARCH) || priv_eff(CAP_DAC_OVERRIDE));
case SCHILY_PRIV_FILE_DAC_WRITE:
return (priv_eff(CAP_DAC_OVERRIDE));
case SCHILY_PRIV_SYS_DEVICES:
return (priv_eff(CAP_SYS_RAWIO) && priv_eff(CAP_SYS_ADMIN));
case SCHILY_PRIV_PROC_LOCK_MEMORY:
return (priv_eff(CAP_IPC_LOCK) && priv_eff(CAP_SYS_RESOURCE));
case SCHILY_PRIV_PROC_PRIOCNTL:
return (priv_eff(CAP_SYS_NICE));
case SCHILY_PRIV_NET_PRIVADDR:
return (priv_eff(CAP_NET_BIND_SERVICE));
}
#endif
#ifndef DID_PRIV
if (geteuid() == 0)
return (TRUE);
#endif
return (FALSE);
#endif
}
#ifdef HAVE_SOLARIS_PPRIV
LOCAL BOOL
priv_eff(pname)
const char *pname;
{
return (priv_ineffect(pname));
}
#endif
#ifdef HAVE_LINUX_CAPS
LOCAL BOOL
priv_eff(pname)
int pname;
{
cap_t cset;
cap_flag_value_t val = CAP_CLEAR;
cset = cap_get_proc();
cap_get_flag(cset, pname, CAP_EFFECTIVE, &val);
if (val == CAP_CLEAR)
return (FALSE);
return (TRUE);
}
#endif
#ifdef HAVE_SOLARIS_PPRIV
#include <schily/varargs.h>
/*
* Allow to compile binaries that work on Solaris 11 build 140 and later
* as well as for older Solaris versions.
* If getpflags(PRIV_PFEXEC) fails, we have no in-kernel pfexec() and we
* just do nothing.
*/
#ifndef PRIV_PFEXEC
#define PRIV_PFEXEC 0x0100
#endif
/*
* If PRIV_PFEXEC is defined, we have an in-kernel pfexec() that allows
* suid-root less installation and let programs gain the needed additional
* privileges even without a wrapper script.
*/
/* VARARGS2 */
#ifdef PROTOTYPES
EXPORT void
do_pfexec(int ac, char *av[], ...)
#else
EXPORT void
do_pfexec(ac, av, va_list)
int ac;
char *av[];
va_decl
#endif
{
#ifdef PRIV_PFEXEC
va_list args;
BOOL priv_ok = TRUE;
priv_set_t *pset;
int oflag;
char *av0;
const char *priv;
/*
* Avoid looping over execv().
* Return if we see our modified argv[0].
* If the first character of the last name component is a '+',
* just leave it as it is. We cannot restore the original character
* in this case. If it is an uppercase character, we assume
* that it was a translated lowercace character from our execv().
*/
av0 = strrchr(av[0], '/');
if (av0 == NULL)
av0 = av[0];
else
av0++;
if (*av0 == '+')
return;
if (*av0 >= 'A' && *av0 <= 'Z') {
*av0 = *av0 + 'a' - 'A';
return;
}
/*
* Check for the current privileges.
* Silently abort attempting to gain more privileges
* in case any error occurs.
*/
pset = priv_allocset();
if (pset == NULL)
return;
if (getppriv(PRIV_EFFECTIVE, pset) < 0)
return;
/*
* If we already have the needed privileges, we are done.
*/
#ifdef PROTOTYPES
va_start(args, av);
#else
va_start(args);
#endif
while (priv_ok && (priv = va_arg(args, const char *)) != NULL) {
if (!priv_ismember(pset, priv))
priv_ok = FALSE;
}
va_end(args);
priv_freeset(pset);
if (priv_ok)
return;
oflag = getpflags(PRIV_PFEXEC);
if (oflag < 0) /* Pre kernel-pfexec system? */
return;
if (oflag == 0) { /* Kernel pfexec flag not yet set? */
/*
* Set kernel pfexec flag.
* Return if this doesn't work for some reason.
*/
if (setpflags(PRIV_PFEXEC, 1) != 0) {
return;
}
}
/*
* Modify argv[0] to mark that we did already call execv().
* This is done in order to avoid infinite execv() loops caused by
* a missconfigured security system in /etc/security.
*
* In the usual case (a lowercase letter in the firsh character of the
* last pathname component), convert it to an uppercase character.
* Otherwise overwrite this character by a '+' sign.
*/
if (*av0 >= 'a' && *av0 <= 'z')
*av0 = *av0 - 'a' + 'A';
else
*av0 = '+';
execv(getexecname(), av);
#endif /* PRIV_PFEXEC */
}
#endif