mirror of https://github.com/rd235/cado
new features: scado and conditions in cado.conf
This commit is contained in:
parent
648d301eb3
commit
bfe686a1c5
44
Makefile.am
44
Makefile.am
|
@ -1,9 +1,45 @@
|
||||||
bin_PROGRAMS = cado caprint
|
bin_PROGRAMS = cado scado caprint
|
||||||
|
|
||||||
cado_SOURCES = cado.c pam_check.c get_user_groups.c capset_from_namelist.c read_conf.c set_ambient_cap.c
|
cado_SOURCES = cado.c pam_check.c get_user_groups.c capset_from_namelist.c read_conf.c set_ambient_cap.c \
|
||||||
|
compute_digest.c file_utils.c scado_parse.c cado_scado_check.c
|
||||||
|
|
||||||
cado_LDADD = -lpam -lpam_misc -lcap
|
cado_LDADD = -lpam -lpam_misc -lcap -lmhash
|
||||||
|
|
||||||
caprint_LDADD = -lcap
|
caprint_LDADD = -lcap
|
||||||
|
|
||||||
man_MANS = cado.1 caprint.1 cado.conf.5
|
scado_SOURCES = scado.c pam_check.c file_utils.c compute_digest.c capset_from_namelist.c scado_parse.c
|
||||||
|
|
||||||
|
scado_LDADD = -lpam -lpam_misc -lcap -lmhash
|
||||||
|
|
||||||
|
common_nodist = cado_paths.h
|
||||||
|
BUILT_SOURCES = $(common_nodist)
|
||||||
|
|
||||||
|
man_MANS = cado.1 caprint.1 scado.1 cado.conf.5
|
||||||
|
|
||||||
|
install-exec-hook:
|
||||||
|
(useradd -r -s /bin/nologin -g `getent passwd | grep cado | cut -f 3 -d ':'` cado ||\
|
||||||
|
useradd -r -s /bin/nologin -U cado) || true
|
||||||
|
(mkdir -p ${SPOOL_DIR} ; chown root:cado ${SPOOL_DIR} && chmod 4770 $(SPOOL_DIR))
|
||||||
|
chown :cado $(DESTDIR)$(bindir)/scado
|
||||||
|
chmod g+s $(DESTDIR)$(bindir)/scado
|
||||||
|
chown cado $(DESTDIR)$(bindir)/cado
|
||||||
|
chmod u+s $(DESTDIR)$(bindir)/cado
|
||||||
|
$(DESTDIR)$(bindir)/cado -s
|
||||||
|
|
||||||
|
CLEANFILES = cado_paths.h
|
||||||
|
cado_paths.h: Makefile
|
||||||
|
@echo 'creating $@'
|
||||||
|
@sed >$@ 's/ *\\$$//' <<\END #\
|
||||||
|
/* This file has been automatically generated. Do not edit. */ \
|
||||||
|
#ifndef _CADO_PATHS_H \
|
||||||
|
#define _CADO_PATHS_H \
|
||||||
|
\
|
||||||
|
/* Spool directory path */ \
|
||||||
|
#define SPOOL_DIR "$(SPOOL_DIR)" \
|
||||||
|
\
|
||||||
|
/* Cado temporary exe directory path */ \
|
||||||
|
#define CADO_EXE_DIR "$(CADO_EXE_DIR)" \
|
||||||
|
\
|
||||||
|
#endif /* _SCADO_PATHS_H */\
|
||||||
|
END
|
||||||
|
|
||||||
|
|
21
cado.1
21
cado.1
|
@ -26,7 +26,7 @@ For brevity, the \fBcap_\fR prefix of capability names can be omitted (e.g. \fBn
|
||||||
have the same meaning).
|
have the same meaning).
|
||||||
|
|
||||||
If it is allowed for the current user to run processes with the requested capabilities, the user is asked to
|
If it is allowed for the current user to run processes with the requested capabilities, the user is asked to
|
||||||
type their password (or to authenticate themselves as required by pam).
|
type their password (or to authenticate themselves as required by pam unless \fB-S\fR or \fB--scado\fR).
|
||||||
Once the authentication succeeds, \fBcado\fR executes the command granting the required ambient capabilities.
|
Once the authentication succeeds, \fBcado\fR executes the command granting the required ambient capabilities.
|
||||||
|
|
||||||
The file /etc/cado.conf (see \fBcado.conf\fR(5)) defines which capabilities can be provided by \fBcado\fR to each user.
|
The file /etc/cado.conf (see \fBcado.conf\fR(5)) defines which capabilities can be provided by \fBcado\fR to each user.
|
||||||
|
@ -34,6 +34,18 @@ Cado itself is not a setuid executable, it uses the capability mechanism and it
|
||||||
set its own capabilities. So after each change in the /etc/cado.conf, the capability set should be
|
set its own capabilities. So after each change in the /etc/cado.conf, the capability set should be
|
||||||
recomputed by root using the command \fBcado -s\fR or \fBcado --setcap\fR.
|
recomputed by root using the command \fBcado -s\fR or \fBcado --setcap\fR.
|
||||||
|
|
||||||
|
When \fBcado\fR runs is scado mode (by the option \fB-S\fR or \fB--scado\fR), if
|
||||||
|
.br
|
||||||
|
\ \ - the current user is allowed to run processes with the requested capabilities,
|
||||||
|
.br
|
||||||
|
\ \ - the \fBcommand\fR argument is an absolute pathname and
|
||||||
|
.br
|
||||||
|
\ \ - there is a specific authorization line in the user's scado file,
|
||||||
|
.br
|
||||||
|
\fBcado\fR runs the command granting the required ambient capabilities without any further authentication request
|
||||||
|
(it does not prompt for a password).
|
||||||
|
.SP
|
||||||
|
|
||||||
.SH OPTIONS
|
.SH OPTIONS
|
||||||
.I cado
|
.I cado
|
||||||
accepts the following options:
|
accepts the following options:
|
||||||
|
@ -55,6 +67,12 @@ set of requested cababilities and the set of allowed capabilities
|
||||||
\fB\-\-setcap
|
\fB\-\-setcap
|
||||||
\fBcado\fR computes the miminal set of capability required by itself and sets the file capability of the cado executable.
|
\fBcado\fR computes the miminal set of capability required by itself and sets the file capability of the cado executable.
|
||||||
.TP
|
.TP
|
||||||
|
\fB\-S
|
||||||
|
.TQ
|
||||||
|
\fB\-\-scado
|
||||||
|
launch \fBcado\fR with \fBscado\fR(1) support. \fRcommand\fI must be an absolute pathname and a specific authorization line must
|
||||||
|
appear in the user's scado file.
|
||||||
|
.TP
|
||||||
\fB\-h
|
\fB\-h
|
||||||
.TQ
|
.TQ
|
||||||
\fB\-\-help
|
\fB\-\-help
|
||||||
|
@ -63,5 +81,6 @@ print a short usage banner and exit.
|
||||||
.SH SEE ALSO
|
.SH SEE ALSO
|
||||||
\fBcado.conf\fR(5),
|
\fBcado.conf\fR(5),
|
||||||
\fBcaprint\fR(1),
|
\fBcaprint\fR(1),
|
||||||
|
\fBscado\fR(1),
|
||||||
\fBcapabilities\fR(7)
|
\fBcapabilities\fR(7)
|
||||||
|
|
||||||
|
|
70
cado.c
70
cado.c
|
@ -25,32 +25,41 @@
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <libgen.h>
|
#include <libgen.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
#include <pam_check.h>
|
#include <pam_check.h>
|
||||||
#include <get_user_groups.h>
|
#include <get_user_groups.h>
|
||||||
#include <capset_from_namelist.h>
|
#include <capset_from_namelist.h>
|
||||||
#include <read_conf.h>
|
#include <read_conf.h>
|
||||||
#include <set_ambient_cap.h>
|
#include <set_ambient_cap.h>
|
||||||
#include <inttypes.h>
|
#include <cado_scado_check.h>
|
||||||
|
|
||||||
|
/* print a capset (in case of -v, verbose mode). */
|
||||||
static void printcapset(uint64_t capset, char *indent) {
|
static void printcapset(uint64_t capset, char *indent) {
|
||||||
|
if (capset) {
|
||||||
cap_value_t cap;
|
cap_value_t cap;
|
||||||
int count=0;
|
int count=0;
|
||||||
for (cap = 0; cap <= CAP_LAST_CAP; cap++) {
|
for (cap = 0; cap <= CAP_LAST_CAP; cap++) {
|
||||||
if (capset & (1ULL << cap)) {
|
if (capset & (1ULL << cap)) {
|
||||||
count ++;
|
count ++;
|
||||||
printf("%s%2d %016llx %s\n",indent,cap,1ULL<<cap,cap_to_name(cap));
|
printf("%s%2d %016" PRIx64 " %s\n", indent, cap, UINT64_C(1) << cap, cap_to_name(cap));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (count > 1)
|
if (count > 1)
|
||||||
printf("%s %016" PRIx64 "\n", indent, capset);
|
printf("%s %016" PRIx64 "\n", indent, capset);
|
||||||
|
} else
|
||||||
|
printf("%s %016" PRIx64 " NONE\n",indent, UINT64_C(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
#define OPTSTRING "hqvs"
|
/* command line args management */
|
||||||
|
#define OPTSTRING "hqvsS"
|
||||||
struct option long_options[]={
|
struct option long_options[]={
|
||||||
{"help", no_argument, NULL, 'h'},
|
{"help", no_argument, NULL, 'h'},
|
||||||
{"quiet", no_argument, NULL, 'q'},
|
{"quiet", no_argument, NULL, 'q'},
|
||||||
{"verbose", no_argument, NULL, 'v'},
|
{"verbose", no_argument, NULL, 'v'},
|
||||||
{"setcap", no_argument, NULL, 'v'},
|
{"setcap", no_argument, NULL, 'v'},
|
||||||
|
{"scado", no_argument, NULL, 'S'}
|
||||||
};
|
};
|
||||||
|
|
||||||
void usage(char *progname) {
|
void usage(char *progname) {
|
||||||
|
@ -60,6 +69,7 @@ void usage(char *progname) {
|
||||||
fprintf(stderr," -h, --help display help message and exit\n");
|
fprintf(stderr," -h, --help display help message and exit\n");
|
||||||
fprintf(stderr," -q, --quiet do not display warnings, do what it is allowed\n");
|
fprintf(stderr," -q, --quiet do not display warnings, do what it is allowed\n");
|
||||||
fprintf(stderr," -v, --verbose generate extra output\n");
|
fprintf(stderr," -v, --verbose generate extra output\n");
|
||||||
|
fprintf(stderr," -S, --scado check scado pre-authorization for scripts\n");
|
||||||
fprintf(stderr," -s, --setcap set the minimun caps for %s (root access)\n",progname);
|
fprintf(stderr," -s, --setcap set the minimun caps for %s (root access)\n",progname);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
@ -69,11 +79,14 @@ int main(int argc, char*argv[])
|
||||||
char *progname=basename(argv[0]);
|
char *progname=basename(argv[0]);
|
||||||
char **user_groups=get_user_groups();
|
char **user_groups=get_user_groups();
|
||||||
uint64_t okcaps;
|
uint64_t okcaps;
|
||||||
uint64_t reqcaps=0;
|
uint64_t reqcaps;
|
||||||
uint64_t grantcap=0;
|
uint64_t grantcap=0;
|
||||||
int verbose=0;
|
int verbose=0;
|
||||||
int quiet=0;
|
int quiet=0;
|
||||||
int setcap=0;
|
int setcap=0;
|
||||||
|
int scado=0;
|
||||||
|
int pam_check_required = 1;
|
||||||
|
char copy_path[PATH_MAX] = "";
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
int c=getopt_long(argc, argv, OPTSTRING, long_options, NULL);
|
int c=getopt_long(argc, argv, OPTSTRING, long_options, NULL);
|
||||||
|
@ -88,12 +101,16 @@ int main(int argc, char*argv[])
|
||||||
break;
|
break;
|
||||||
case 's': setcap=1;
|
case 's': setcap=1;
|
||||||
break;
|
break;
|
||||||
|
case 'S': scado=1;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* setcap mode: cado sets the minimal required set of capability required by itself */
|
||||||
|
|
||||||
if (setcap) {
|
if (setcap) {
|
||||||
if (geteuid() != 0) {
|
if (setuid(0) != 0 || geteuid() != 0) {
|
||||||
fprintf(stderr, "setcap requires root access\n");
|
fprintf(stderr, "setcap requires root access %d\n",geteuid());
|
||||||
exit(2);
|
exit(2);
|
||||||
}
|
}
|
||||||
okcaps = get_authorized_caps(NULL, -1LL);
|
okcaps = get_authorized_caps(NULL, -1LL);
|
||||||
|
@ -109,6 +126,12 @@ int main(int argc, char*argv[])
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (user_groups == NULL) {
|
||||||
|
fprintf(stderr, "No passwd entry for user '%d'\n",getuid());
|
||||||
|
exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -v without any other parameter: cado shows the set of ambient capabilities allowed for the current user/group */
|
||||||
if (verbose && (argc == optind)) {
|
if (verbose && (argc == optind)) {
|
||||||
okcaps=get_authorized_caps(user_groups, -1LL);
|
okcaps=get_authorized_caps(user_groups, -1LL);
|
||||||
printf("Allowed ambient capabilities:\n");
|
printf("Allowed ambient capabilities:\n");
|
||||||
|
@ -119,21 +142,40 @@ int main(int argc, char*argv[])
|
||||||
if (argc - optind < 2)
|
if (argc - optind < 2)
|
||||||
usage(progname);
|
usage(progname);
|
||||||
|
|
||||||
if (capset_from_namelist(argv[optind], &reqcaps))
|
/* parse the set of requested capabilities */
|
||||||
|
if (capset_from_namelist(argv[optind], &reqcaps)) {
|
||||||
|
fprintf(stderr, "List of capabilities: syntax error\n");
|
||||||
exit(2);
|
exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
printf("Requested ambient capabilities:\n");
|
printf("Requested ambient capabilities:\n");
|
||||||
printcapset(reqcaps, " ");
|
printcapset(reqcaps, " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* check if the capability requested are also allowed */
|
||||||
okcaps=get_authorized_caps(user_groups, reqcaps);
|
okcaps=get_authorized_caps(user_groups, reqcaps);
|
||||||
|
|
||||||
|
optind++;
|
||||||
|
|
||||||
|
/* scado mode, check if there is a pre-authorization for the command */
|
||||||
|
if (scado) {
|
||||||
|
uint64_t scado_caps = cado_scado_check(user_groups[0], argv[optind], copy_path);
|
||||||
|
if (verbose) {
|
||||||
|
printf("Scado permitted capabilities for %s:\n", argv[optind]);
|
||||||
|
printcapset(scado_caps, " ");
|
||||||
|
}
|
||||||
|
okcaps &= scado_caps;
|
||||||
|
pam_check_required = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the user requested capabilities which are not allowed */
|
||||||
if (reqcaps & ~okcaps) {
|
if (reqcaps & ~okcaps) {
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
printf("Unavailable ambient capabilities:\n");
|
printf("Unavailable ambient capabilities:\n");
|
||||||
printcapset(reqcaps & ~okcaps, " ");
|
printcapset(reqcaps & ~okcaps, " ");
|
||||||
}
|
}
|
||||||
|
/* if not in "quiet" mode, do not complaint */
|
||||||
if (!quiet) {
|
if (!quiet) {
|
||||||
fprintf(stderr,"%s: Permission denied\n",progname);
|
fprintf(stderr,"%s: Permission denied\n",progname);
|
||||||
exit(2);
|
exit(2);
|
||||||
|
@ -142,17 +184,25 @@ int main(int argc, char*argv[])
|
||||||
|
|
||||||
grantcap = reqcaps & okcaps;
|
grantcap = reqcaps & okcaps;
|
||||||
|
|
||||||
optind++;
|
/* revert setgid mode */
|
||||||
|
setuid(getuid());
|
||||||
|
|
||||||
if (pam_check(user_groups[0]) != 0) {
|
/* ask for pam authorization (usually password) if required */
|
||||||
|
if (pam_check_required && pam_check(user_groups[0]) != PAM_SUCCESS) {
|
||||||
fprintf(stderr,"%s: Authentication failure\n",progname);
|
fprintf(stderr,"%s: Authentication failure\n",progname);
|
||||||
exit(2);
|
exit(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* okay: grantcap can be granted, do it! */
|
||||||
|
if (grantcap)
|
||||||
set_ambient_cap(grantcap);
|
set_ambient_cap(grantcap);
|
||||||
|
|
||||||
if (verbose && (reqcaps & ~okcaps)) {
|
if (verbose && (reqcaps & ~okcaps)) {
|
||||||
printf("Granted ambient capabilities:\n");
|
printf("Granted ambient capabilities:\n");
|
||||||
printcapset(grantcap, " ");
|
printcapset(grantcap, " ");
|
||||||
}
|
}
|
||||||
execvp(argv[optind],argv+optind);
|
|
||||||
|
/* exec the command in the new ambient capability environment */
|
||||||
|
execvp(copy_path[0] == 0 ? argv[optind] : copy_path, argv+optind);
|
||||||
exit(2);
|
exit(2);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
#ifndef _CADO_CONST_H
|
||||||
|
#define _CADO_CONST_H
|
||||||
|
|
||||||
|
#include <cado_paths.h>
|
||||||
|
|
||||||
|
/* Default message to print in a new scado file. */
|
||||||
|
#define DEFAULT_MESSAGE \
|
||||||
|
"# This is a scado file.\n"\
|
||||||
|
"# format: executable : capabilities_list [:]\n"\
|
||||||
|
"# If you specify :,\n"\
|
||||||
|
"# scado will automatically put the checksum of the file at the end of the line\n"\
|
||||||
|
"# (for subsequent checks).\n"
|
||||||
|
|
||||||
|
/* Tmp file template */
|
||||||
|
#define TMP_TEMPLATE "cado-scado.XXXXXX"
|
||||||
|
|
||||||
|
/* Tmp Directory */
|
||||||
|
#define TMPDIR "/tmp"
|
||||||
|
|
||||||
|
#define BUFSIZE 4096
|
||||||
|
|
||||||
|
#define DIGEST_WARNING "***********************************************************\n"\
|
||||||
|
"* WARNING: EXECUTABLE DIGEST HAS CHANGED! *\n"\
|
||||||
|
"***********************************************************\n"\
|
||||||
|
"IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!\n"\
|
||||||
|
"Someone could be changed your executable, and it's trying to execute a malware!\n"\
|
||||||
|
"It is also possible that the executable is just changed (maybe a system upgrade?).\n"
|
||||||
|
|
||||||
|
#define COPY_DIR CADO_EXE_DIR
|
||||||
|
|
||||||
|
#define COPY_TEMPLATE "cado-exe-copy.XXXXXX"
|
||||||
|
|
||||||
|
#define COMMENT_LINE 0
|
||||||
|
#define NO_CHECKSUM_LINE 1
|
||||||
|
#define CALCULATE_CHECKSUM_LINE 2
|
||||||
|
#define CHECKSUM_LINE 3
|
||||||
|
#define NOT_CONSIDERED_LINE 4
|
||||||
|
|
||||||
|
#endif /* _CADO_CONST_H */
|
|
@ -0,0 +1,11 @@
|
||||||
|
/* This file has been automatically generated. Do not edit. */
|
||||||
|
#ifndef _CADO_PATHS_H
|
||||||
|
#define _CADO_PATHS_H
|
||||||
|
|
||||||
|
/* Spool directory path */
|
||||||
|
#define SPOOL_DIR "/usr/local/var/spool/cado"
|
||||||
|
|
||||||
|
/* Cado temporary exe directory path */
|
||||||
|
#define CADO_EXE_DIR "/tmp"
|
||||||
|
|
||||||
|
#endif /* _SCADO_PATHS_H */
|
|
@ -0,0 +1,213 @@
|
||||||
|
/*
|
||||||
|
* cado: execute a command in a capability ambient
|
||||||
|
* Copyright (C) 2016 Davide Berardi and Renzo Davoli, University of Bologna
|
||||||
|
*
|
||||||
|
* This file is part of cado.
|
||||||
|
*
|
||||||
|
* Cado is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/capability.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
|
#include <cado_const.h>
|
||||||
|
#include <get_scado_file.h>
|
||||||
|
#include <set_ambient_cap.h>
|
||||||
|
#include <scado_parse.h>
|
||||||
|
#include <compute_digest.h>
|
||||||
|
#include <cado_scado_check.h>
|
||||||
|
|
||||||
|
static void print_digest_warning(void) {
|
||||||
|
fprintf(stderr, "%s", DIGEST_WARNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Avoiding TOCTOU attacks:
|
||||||
|
The executable file is copied in a safe place where the user cannot modify it.
|
||||||
|
(cado is setuid cado).
|
||||||
|
The digest of the copy is compared against the digest reported in the scado configuration file.
|
||||||
|
If the digests are the same, cado runs the copy */
|
||||||
|
/* A watchdog process unlinks the copy as soon as either cado terminates or has started the command (execve system call) */
|
||||||
|
|
||||||
|
/* copy_and_check_digest returns 0 if it succeeds, i.e.
|
||||||
|
- a file at path exists and it is readable
|
||||||
|
- the hash digest of the file is equal to digest
|
||||||
|
and in this case newpath returns the newpath of the tmpfile to run */
|
||||||
|
/* if scado does not require to check the digest, copy_and_check_digest
|
||||||
|
returns an empty string in newpath, i.e *newpath == 0 */
|
||||||
|
/* if copy_and_check_digest fails, it returns -1 */
|
||||||
|
static int copy_and_check_digest(const char *path, char *digest, char *newpath) {
|
||||||
|
size_t newpathlen;
|
||||||
|
/* grandchild -> grandparent pipe:
|
||||||
|
write the path of the tmp copy and close
|
||||||
|
in case of error: close (without writing anything)
|
||||||
|
in case of hash mismatch write a NULL byte and close */
|
||||||
|
int gc_pipe[2];
|
||||||
|
pid_t childpid;
|
||||||
|
|
||||||
|
/* grandparent -> grandchild pipe: used to check when the exec occours. */
|
||||||
|
/* this pipe has the FD_CLOEXEC bit set.
|
||||||
|
it is unvoluntarily closed either when an exec succeeds or when the grandparent terminates
|
||||||
|
(end or abend, it does not matter) */
|
||||||
|
/* when the grandchild gets the EOF it unlinks the temporary file */
|
||||||
|
int gp_pipe[2];
|
||||||
|
|
||||||
|
if (!path || !digest || !newpath) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snprintf(newpath, PATH_MAX, "%s/%s", COPY_DIR, COPY_TEMPLATE) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
newpathlen = strlen(newpath);
|
||||||
|
|
||||||
|
if (pipe(gc_pipe)) {
|
||||||
|
perror("pipe");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pipe(gp_pipe)) {
|
||||||
|
perror("pipe");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fcntl(gp_pipe[0], F_SETFD, FD_CLOEXEC) ||
|
||||||
|
fcntl(gp_pipe[1], F_SETFD, FD_CLOEXEC)) {
|
||||||
|
perror("fdcntl cloexec");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Start garbage collector
|
||||||
|
* Double fork to avoid zombie processes and to remove the temporary file when it is not needed any more */
|
||||||
|
if(!(childpid=fork())) {
|
||||||
|
/* Child */
|
||||||
|
if(!fork()) {
|
||||||
|
/* Grandchild */
|
||||||
|
char newdigest[DIGESTSTRLEN + 1];
|
||||||
|
char c;
|
||||||
|
int exit_status = 1;
|
||||||
|
|
||||||
|
/* if the grandparent terminates before reading the newpath,
|
||||||
|
write returns EPIPE. So even in this situation the garbage gets collected i.e. unlink(newpath)*/
|
||||||
|
/* setsid to avoid the propagation of signals to the process group (e.g. ^C) */
|
||||||
|
if (close(gc_pipe[0]) || close(gp_pipe[1]) || setsid() < 0 || signal(SIGPIPE, SIG_IGN) == SIG_ERR)
|
||||||
|
exit(-1);
|
||||||
|
|
||||||
|
if (copytemp_digest(path, newpath, newdigest) < 0) {
|
||||||
|
perror("copytemp_hash");
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
//debug only
|
||||||
|
//printf("GS %s\n%s\n%s\n", newpath,digest,newdigest);
|
||||||
|
if (chmod(newpath, 00611) < 0)
|
||||||
|
goto end;
|
||||||
|
|
||||||
|
if (strcmp(digest,newdigest) == 0) {
|
||||||
|
if (write(gc_pipe[1], newpath, newpathlen + 1) < 1)
|
||||||
|
goto end;
|
||||||
|
} else {
|
||||||
|
char err = 0;
|
||||||
|
if (write(gc_pipe[1], &err, 1) < 1)
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (close(gc_pipe[1]))
|
||||||
|
goto end;
|
||||||
|
|
||||||
|
/* No data should be written on the pipe from the other end. This is only
|
||||||
|
* to check when the parent calls exec() (or terminates)*/
|
||||||
|
while (read(gp_pipe[0], &c, sizeof(char)) > 0)
|
||||||
|
;
|
||||||
|
|
||||||
|
close(gp_pipe[0]);
|
||||||
|
exit_status = 0;
|
||||||
|
end:
|
||||||
|
unlink(newpath);
|
||||||
|
exit(exit_status);
|
||||||
|
} else {
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
waitpid(childpid, NULL, 0);
|
||||||
|
|
||||||
|
/* (grand) Parent */
|
||||||
|
if (close(gc_pipe[1]) || close(gp_pipe[0])) {
|
||||||
|
perror("close pipe");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* it should read a string of the same length of the original newpath which is the
|
||||||
|
template for the tmp file (as described in mkstemp(3) */
|
||||||
|
int n;
|
||||||
|
if ((n=read(gc_pipe[0], newpath, newpathlen + 1)) <= 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (close(gc_pipe[0])) {
|
||||||
|
perror("close");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newpath[0] == 0) {
|
||||||
|
print_digest_warning();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* given the username and the command path, cado_scado_check returns the set of
|
||||||
|
permitted capabilities, as defined by the scado(1) command */
|
||||||
|
uint64_t cado_scado_check(const char *username, const char *exe_path, char *new_path) {
|
||||||
|
char scado_file[PATH_MAX];
|
||||||
|
char digest[DIGESTSTRLEN + 1];
|
||||||
|
int rv;
|
||||||
|
uint64_t capset = 0;
|
||||||
|
|
||||||
|
if (!username || !exe_path || !new_path)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (get_scado_file(username, scado_file) <= 0){
|
||||||
|
perror("get scado file");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
raise_cap_dac_read_search();
|
||||||
|
rv = scado_path_getinfo(scado_file, exe_path, &capset, digest);
|
||||||
|
lower_cap_dac_read_search();
|
||||||
|
|
||||||
|
/* default value: do not run a copy, directly run the command */
|
||||||
|
*new_path = 0;
|
||||||
|
if (rv <= 0) {
|
||||||
|
/* error: no capabilities canbe granted */
|
||||||
|
if (rv < 0)
|
||||||
|
perror("error opening scado file");
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
/* if no digest was specified in the scado configuration line for the current command:
|
||||||
|
the capabilities in capset can be granted.
|
||||||
|
otherwise copy the executable file to avoid TOCTOU attacks */
|
||||||
|
if (*digest == 0 || copy_and_check_digest(exe_path, digest, new_path) == 0)
|
||||||
|
return capset;
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
#ifndef CADO_SCADO_CHECK_H
|
||||||
|
#define CADO_SCADO_CHECK_H
|
||||||
|
|
||||||
|
uint64_t cado_scado_check(const char *username, const char *exe_path, char *new_path);
|
||||||
|
|
||||||
|
#endif
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* cado: execute a command in a capability ambient
|
* caprint: print the set of ambient capability of a process.
|
||||||
* Copyright (C) 2016 Renzo Davoli, University of Bologna
|
* Copyright (C) 2016 Renzo Davoli, University of Bologna
|
||||||
*
|
*
|
||||||
* This file is part of cado.
|
* This file is part of cado.
|
||||||
|
@ -48,7 +48,7 @@ uint64_t get_capamb(pid_t pid) {
|
||||||
status++;
|
status++;
|
||||||
if (status == target) {
|
if (status == target) {
|
||||||
int fields = 0;
|
int fields = 0;
|
||||||
if ((fields = fscanf(f,"%" PRIx64 "",&capamb)) != 1)
|
if ((fields = fscanf(f,"%" SCNx64 "",&capamb)) != 1)
|
||||||
fprintf(stderr, "WARNING: fscanf on %s return %d fields.\n", filename, fields);
|
fprintf(stderr, "WARNING: fscanf on %s return %d fields.\n", filename, fields);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,12 +48,16 @@ static int addcap(char *name, uint64_t *capset) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* convert a list of comma separated capability tags to a bitmask of capabilities */
|
||||||
|
/* capset_from_namelist allows capability names with or without the "cap_" prefix. */
|
||||||
int capset_from_namelist(char *namelist, uint64_t *capset) {
|
int capset_from_namelist(char *namelist, uint64_t *capset) {
|
||||||
int rv=0;
|
int rv=0;
|
||||||
char *onecap;
|
char *onecap;
|
||||||
char *tmptok = NULL;
|
char *tmptok;
|
||||||
|
char *spacetok;
|
||||||
|
*capset = 0;
|
||||||
for (; (onecap = strtok_r(namelist,",",&tmptok)) != NULL; namelist = NULL)
|
for (; (onecap = strtok_r(namelist,",",&tmptok)) != NULL; namelist = NULL)
|
||||||
rv |= addcap(onecap,capset);
|
rv |= addcap(strtok_r(onecap," \t",&spacetok), capset);
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
* scado: setuid in capability sauce.
|
||||||
|
* Copyright (C) 2016 Davide Berardi and Renzo Davoli, University of Bologna
|
||||||
|
*
|
||||||
|
* This file is part of cado.
|
||||||
|
*
|
||||||
|
* Cado is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <compute_digest.h>
|
||||||
|
#define BUFSIZE 1024
|
||||||
|
|
||||||
|
/* compute the hash digest for file whose descriptor is infd.
|
||||||
|
if outfd >= 0 copy the contents to the file whose descriptor is outfd.
|
||||||
|
the return value is the size of the file (or a negative value in case of error).
|
||||||
|
the size of ascii_digest must be at least DIGESTSTRLEN + 1*/
|
||||||
|
static ssize_t fcompute_digest(int infd, int outfd, char *ascii_digest) {
|
||||||
|
char buf[BUFSIZE];
|
||||||
|
unsigned char binary_digest[DIGESTLEN];
|
||||||
|
MHASH td;
|
||||||
|
|
||||||
|
ssize_t n;
|
||||||
|
ssize_t rv = 0;
|
||||||
|
td = mhash_init(DIGESTTYPE);
|
||||||
|
|
||||||
|
while ((n=read(infd,buf,BUFSIZE)) > 0) {
|
||||||
|
mhash(td, buf, n);
|
||||||
|
if (outfd >= 0) write(outfd, buf, n);
|
||||||
|
rv += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
mhash_deinit(td, binary_digest);
|
||||||
|
if (n >= 0) {
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < DIGESTLEN; i++, ascii_digest += 2)
|
||||||
|
sprintf(ascii_digest, "%.2x", binary_digest[i]);
|
||||||
|
*ascii_digest = 0;
|
||||||
|
}
|
||||||
|
return (n < 0) ? n : rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* compute the hash digest of the file (the first arg is the pathname) */
|
||||||
|
ssize_t compute_digest(const char *path, char *digest) {
|
||||||
|
int fd=open(path, O_RDONLY);
|
||||||
|
if (fd >= 0) {
|
||||||
|
ssize_t rv = fcompute_digest(fd, -1, digest);
|
||||||
|
close(fd);
|
||||||
|
return rv;
|
||||||
|
} else
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* compute the hash digest of the file (the first arg is the pathname)
|
||||||
|
while copying it in a temporary file. The second arg is a template for tmp files
|
||||||
|
as explained in mkstemp(3)
|
||||||
|
*/
|
||||||
|
ssize_t copytemp_digest(const char *inpath, char *outtemplate, char *digest) {
|
||||||
|
int infd=open(inpath, O_RDONLY);
|
||||||
|
int outfd;
|
||||||
|
mode_t oldmask;
|
||||||
|
ssize_t rv;
|
||||||
|
if (infd < 0)
|
||||||
|
return -1;
|
||||||
|
oldmask = umask(027);
|
||||||
|
outfd = mkstemp(outtemplate);
|
||||||
|
umask(oldmask);
|
||||||
|
if (outfd < 0)
|
||||||
|
return -1;
|
||||||
|
rv = fcompute_digest(infd, outfd, digest);
|
||||||
|
close(infd);
|
||||||
|
close(outfd);
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
#ifndef _COMPUTE_DIGEST_H
|
||||||
|
#define _COMPUTE_DIGEST_H
|
||||||
|
#include <mhash.h>
|
||||||
|
|
||||||
|
#define DIGESTTYPE MHASH_SHA256
|
||||||
|
#define DIGESTLEN (mhash_get_block_size(DIGESTTYPE))
|
||||||
|
#define DIGESTSTRLEN (2*DIGESTLEN)
|
||||||
|
|
||||||
|
/* compute the hash digest.
|
||||||
|
store the result (hex string) in hashstr: an array of at least DIGESTSTRLEN chars
|
||||||
|
return the size of the file in case of success, -1 in case of error */
|
||||||
|
ssize_t compute_digest(const char *path, char *hashstr);
|
||||||
|
|
||||||
|
/* copytemp_hash copies the file whose path is 'inpath' in a temporary file whose path is
|
||||||
|
created by mkstemp using 'outtemplate' */
|
||||||
|
/* During the copy, copytemp_hash computes the hash and stores the (hex) result in hashstr */
|
||||||
|
ssize_t copytemp_digest(const char *inpath, char *outtemplate, char *hashstr);
|
||||||
|
|
||||||
|
#endif /* _COMPUTE_DIGEST_H */
|
||||||
|
|
27
configure.ac
27
configure.ac
|
@ -2,10 +2,11 @@
|
||||||
# Process this file with autoconf to produce a configure script.
|
# Process this file with autoconf to produce a configure script.
|
||||||
|
|
||||||
AC_PREREQ([2.69])
|
AC_PREREQ([2.69])
|
||||||
AC_INIT([cado], [0.9], [info@v2.cs.unibo.it])
|
AC_INIT([cado], [0.9.1], [info@v2.cs.unibo.it])
|
||||||
AM_INIT_AUTOMAKE([foreign dist-bzip2])
|
AM_INIT_AUTOMAKE([foreign dist-bzip2])
|
||||||
AC_CONFIG_SRCDIR([pam_check.h])
|
AC_CONFIG_SRCDIR([pam_check.h])
|
||||||
AC_CONFIG_HEADERS([config.h])
|
AC_CONFIG_HEADERS([config.h])
|
||||||
|
CFLAGS="$CFLAGS -Wall"
|
||||||
|
|
||||||
# Checks for programs.
|
# Checks for programs.
|
||||||
AC_PROG_CC
|
AC_PROG_CC
|
||||||
|
@ -14,7 +15,7 @@ AC_PROG_INSTALL
|
||||||
# Checks for libraries.
|
# Checks for libraries.
|
||||||
AC_CHECK_LIB([s2argv], [s2argv], [],
|
AC_CHECK_LIB([s2argv], [s2argv], [],
|
||||||
[
|
[
|
||||||
AC_MSG_ERROR([Could not find S2ARGV library])
|
AC_MSG_ERROR([Could not find S2ARGV library (https://github.com/rd235/s2argv-execs)])
|
||||||
])
|
])
|
||||||
|
|
||||||
# Checks for header files.
|
# Checks for header files.
|
||||||
|
@ -38,4 +39,26 @@ AC_TYPE_UINT64_T
|
||||||
# Checks for library functions.
|
# Checks for library functions.
|
||||||
AC_CHECK_FUNCS([strdup strtoull])
|
AC_CHECK_FUNCS([strdup strtoull])
|
||||||
|
|
||||||
|
AC_DEFUN([CADO_CONF_VAR],
|
||||||
|
[AC_ARG_VAR([$1], [$2 @<:@$3@:>@])
|
||||||
|
if test "$$1" = ""; then
|
||||||
|
$1='$3'
|
||||||
|
fi
|
||||||
|
])
|
||||||
|
|
||||||
|
AC_ARG_WITH([editor],
|
||||||
|
[AC_HELP_STRING([--with-editor=EDITOR], [path to default editor])],
|
||||||
|
[editor_defined="$with-editor"],
|
||||||
|
[editor_defined="no"])
|
||||||
|
|
||||||
|
AS_IF([test "x$editor_defined" = "xno"], [
|
||||||
|
AC_PATH_PROG([editor_defined], [vi], [/usr/bin/vi])
|
||||||
|
])
|
||||||
|
|
||||||
|
AC_DEFINE_UNQUOTED([EDITOR], ["$editor_defined"], [default editor])
|
||||||
|
|
||||||
|
# Set the paths.
|
||||||
|
CADO_CONF_VAR([SPOOL_DIR], [the directory where all the user scado files reside],[${localstatedir}/spool/cado])
|
||||||
|
CADO_CONF_VAR([CADO_EXE_DIR], [the directory where all the temporary executable files reside],[/tmp])
|
||||||
|
|
||||||
AC_OUTPUT([Makefile])
|
AC_OUTPUT([Makefile])
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
* cado: execute a command in a capability ambient
|
||||||
|
* Copyright (C) 2016 Renzo Davoli, University of Bologna
|
||||||
|
*
|
||||||
|
* This file is part of cado.
|
||||||
|
*
|
||||||
|
* Cado is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#undef __linux__
|
||||||
|
#ifdef __linux__
|
||||||
|
#include <sys/sendfile.h>
|
||||||
|
#define MAXSENDFILE 0x7ffff000
|
||||||
|
#else
|
||||||
|
#define BUFSIZE 1024
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* copy a file: when possible use a fast system call */
|
||||||
|
ssize_t copyfile(int infd, int outfd) {
|
||||||
|
ssize_t rv=0;
|
||||||
|
ssize_t n;
|
||||||
|
#ifdef __linux__
|
||||||
|
do
|
||||||
|
rv += n = sendfile(outfd, infd, NULL, MAXSENDFILE);
|
||||||
|
while (n == MAXSENDFILE);
|
||||||
|
#else
|
||||||
|
static int pagesize;
|
||||||
|
if (__builtin_expect((pagesize == 0),0)) pagesize=sysconf(_SC_PAGESIZE);
|
||||||
|
char buf[BUFSIZE];
|
||||||
|
while ((n = read(infd, buf, BUFSIZE)) > 0)
|
||||||
|
rv += write(outfd, buf, n);
|
||||||
|
#endif
|
||||||
|
return (n < 0) ? n : rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* create a temporary copy of a file (using copyfile) */
|
||||||
|
/* outtemplate is a template for temporary files as explained in mkstemp(3) */
|
||||||
|
ssize_t copytemp(char *inpath, char *outtemplate) {
|
||||||
|
int infd=open(inpath, O_RDONLY);
|
||||||
|
int outfd;
|
||||||
|
mode_t oldmask;
|
||||||
|
ssize_t rv;
|
||||||
|
if (infd < 0)
|
||||||
|
return -1;
|
||||||
|
oldmask = umask(077);
|
||||||
|
outfd = mkstemp(outtemplate);
|
||||||
|
umask(oldmask);
|
||||||
|
if (outfd < 0) {
|
||||||
|
close(infd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
rv = copyfile(infd, outfd);
|
||||||
|
close(infd);
|
||||||
|
close(outfd);
|
||||||
|
return rv;
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
#ifndef _FILE_UTILS_H
|
||||||
|
#define _FILE_UTILS_H
|
||||||
|
|
||||||
|
/* copyfile copies the file infd (from the current offset) to
|
||||||
|
outfd (from the current offset) */
|
||||||
|
/* the return value is the number of bytes copied */
|
||||||
|
ssize_t copyfile(int infd, int outfd);
|
||||||
|
|
||||||
|
/* copytemp copies the file whose path is 'inpath' in a temporary file
|
||||||
|
whose pathname has been created by mkstemp(3) using 'outtemplate; */
|
||||||
|
ssize_t copytemp(char *inpath, char *outtemplate);
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,11 @@
|
||||||
|
#ifndef _GET_SPOOL_FILE_H
|
||||||
|
#define _GET_SPOOL_FILE_H
|
||||||
|
#include <limits.h>
|
||||||
|
#include <cado_paths.h>
|
||||||
|
|
||||||
|
/* Get the user scado file */
|
||||||
|
static inline int get_scado_file(const char *username, char *path) {
|
||||||
|
return snprintf(path, PATH_MAX, "%s/%s", SPOOL_DIR, username);
|
||||||
|
}
|
||||||
|
#endif /* _GET_SPOOL_FILE_H */
|
||||||
|
|
|
@ -27,11 +27,18 @@
|
||||||
#include <grp.h>
|
#include <grp.h>
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
|
|
||||||
|
/* if the user does not exist get_user_groups returns NULL,
|
||||||
|
otherwise it returns a dynamic allocated array whose
|
||||||
|
first element (0) is the username of the current user.
|
||||||
|
The following elements are the names of all the groups the current user belongs to.
|
||||||
|
A NULL element tags the end of the array */
|
||||||
char **get_user_groups(void) {
|
char **get_user_groups(void) {
|
||||||
uid_t uid=getuid();
|
uid_t uid=getuid();
|
||||||
struct passwd *pwd=getpwuid(uid);
|
struct passwd *pwd=getpwuid(uid);
|
||||||
int ngroups=0;
|
int ngroups=0;
|
||||||
char **user_groups=NULL;
|
char **user_groups=NULL;
|
||||||
|
if (pwd == NULL)
|
||||||
|
return NULL;
|
||||||
getgrouplist(pwd->pw_name, pwd->pw_gid, NULL, &ngroups);
|
getgrouplist(pwd->pw_name, pwd->pw_gid, NULL, &ngroups);
|
||||||
if (ngroups > 0) {
|
if (ngroups > 0) {
|
||||||
gid_t gids[ngroups];
|
gid_t gids[ngroups];
|
||||||
|
|
|
@ -27,13 +27,17 @@
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
#include <pam_check.h>
|
#include <pam_check.h>
|
||||||
|
|
||||||
|
/* call PAM to authorize the current user.
|
||||||
|
usually this means to prompt the user for a password,
|
||||||
|
but it can be configured using PAM */
|
||||||
|
|
||||||
int pam_check(char *username)
|
int pam_check(char *username)
|
||||||
{
|
{
|
||||||
pam_handle_t* pamh;
|
pam_handle_t* pamh;
|
||||||
struct pam_conv pamc={.conv=&misc_conv, .appdata_ptr=NULL};
|
struct pam_conv pamc={.conv=&misc_conv, .appdata_ptr=NULL};
|
||||||
int rv;
|
int rv;
|
||||||
|
|
||||||
pam_start ("capdo", username, &pamc, &pamh);
|
pam_start ("cado", username, &pamc, &pamh);
|
||||||
rv= pam_authenticate (pamh, 0);
|
rv= pam_authenticate (pamh, 0);
|
||||||
pam_end (pamh, 0);
|
pam_end (pamh, 0);
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
#ifndef PAM_CHECK_H
|
#ifndef PAM_CHECK_H
|
||||||
#define PAM_CHECK_H
|
#define PAM_CHECK_H
|
||||||
|
#include <security/pam_appl.h>
|
||||||
|
|
||||||
int pam_check(char *username);
|
int pam_check(char *username);
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -37,6 +37,8 @@
|
||||||
|
|
||||||
#define CADO_CONF CONFDIR "/cado.conf"
|
#define CADO_CONF CONFDIR "/cado.conf"
|
||||||
|
|
||||||
|
/* cado.conf management */
|
||||||
|
|
||||||
/* groupmatch returns 1 if group belongs to grouplist */
|
/* groupmatch returns 1 if group belongs to grouplist */
|
||||||
static int groupmatch (char *group, char **grouplist) {
|
static int groupmatch (char *group, char **grouplist) {
|
||||||
for (;*grouplist; grouplist++) {
|
for (;*grouplist; grouplist++) {
|
||||||
|
@ -64,10 +66,10 @@ uint64_t get_authorized_caps(char **user_groups, uint64_t reqset) {
|
||||||
f=fopen(CADO_CONF, "r");
|
f=fopen(CADO_CONF, "r");
|
||||||
if (f) {
|
if (f) {
|
||||||
char *line=NULL;
|
char *line=NULL;
|
||||||
ssize_t len,n=0;
|
size_t n=0;
|
||||||
/* set s2argv security, children must drop their capabilities */
|
/* set s2argv security, children must drop their capabilities */
|
||||||
s2_fork_security=drop_capabilities;
|
s2_fork_security=drop_capabilities;
|
||||||
while ((len=getline(&line, &n, f)) > 0 && (reqset & ~ok_caps)) {
|
while (getline(&line, &n, f) > 0 && (reqset & ~ok_caps)) {
|
||||||
//printf("%s",line);
|
//printf("%s",line);
|
||||||
char *scan=line;
|
char *scan=line;
|
||||||
char *tokencap;
|
char *tokencap;
|
||||||
|
@ -87,7 +89,6 @@ uint64_t get_authorized_caps(char **user_groups, uint64_t reqset) {
|
||||||
//printf("UG %s\n",tokenusergroup);
|
//printf("UG %s\n",tokenusergroup);
|
||||||
tokencondition=strtok_r(NULL, ":\n", &tmptok);
|
tokencondition=strtok_r(NULL, ":\n", &tmptok);
|
||||||
//printf("COND %s\n",tokencondition);
|
//printf("COND %s\n",tokencondition);
|
||||||
capset=0;
|
|
||||||
if (capset_from_namelist(tokencap, &capset) < 0)
|
if (capset_from_namelist(tokencap, &capset) < 0)
|
||||||
continue;
|
continue;
|
||||||
if (user_groups == NULL) {
|
if (user_groups == NULL) {
|
||||||
|
|
|
@ -0,0 +1,140 @@
|
||||||
|
.TH SCADO 1 "June 23, 2016" "VirtualSquare Labs"
|
||||||
|
.SH NAME
|
||||||
|
scado \- Script Capability Ambient DO
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.B scado
|
||||||
|
.B -D
|
||||||
|
|
|
||||||
|
.B -e
|
||||||
|
|
|
||||||
|
.B -l
|
||||||
|
.br
|
||||||
|
.B scado
|
||||||
|
.B -u
|
||||||
|
.I command
|
||||||
|
|
|
||||||
|
.B -U
|
||||||
|
.br
|
||||||
|
.B scado
|
||||||
|
.B -h
|
||||||
|
|
||||||
|
.SH DESCRIPTION
|
||||||
|
|
||||||
|
\fBcado(1)\fR permits to delegate capabilities to users.
|
||||||
|
Users can grant a subset of these ambient capabilities to trusted programs.
|
||||||
|
Each user can define their own list of trusted programs and which capabilities to grant, using a scado file.
|
||||||
|
\fBcado -S\fR or \fBcado --scado\fR run those trusted programs without any further authentication.
|
||||||
|
In this way it is also possible to run programs requiring specific capabilities within a bash script.
|
||||||
|
|
||||||
|
\fBScado\fR is the command a user can run to create, edit, check or delete their own scado file.
|
||||||
|
|
||||||
|
Each line of a scado file file has the following syntax:
|
||||||
|
.br
|
||||||
|
.RS 4
|
||||||
|
.I path_of_the_executable_file : capability_list
|
||||||
|
.RE
|
||||||
|
.br
|
||||||
|
or
|
||||||
|
.br
|
||||||
|
.RS 4
|
||||||
|
.I path_of_the_executable_file : capability_list : sha256_digest_of_the_executable
|
||||||
|
.RE
|
||||||
|
.br
|
||||||
|
(See the EXAMPLES section at the end f the man page for more info. All the trailing part of a line following a # sign is a comment.).
|
||||||
|
|
||||||
|
The \fIpath_of_the_executable_file\fR must be absolute.
|
||||||
|
|
||||||
|
The \fIcapability_list\fR is a comma separated list of capability names or capability
|
||||||
|
masks. For brevity, the \fBcap_\fR prefix of capabilities names can be omitted
|
||||||
|
(e.g. \fBnet_admin\fR and \fBcap_net_admin\fR have the same meaning).
|
||||||
|
|
||||||
|
The \fIsha256_digest_of_the_executable\fR prevents \fITOCTTOU\fR attacks. When a user
|
||||||
|
wants to run the file at \fIpath_of_the_executable_file\fR granting it some of
|
||||||
|
the capabilities in the \fIcapability_list\fR, the permission is denied if
|
||||||
|
its sha256 digest does not match \fIsha256_digest_of_the_executable\R.
|
||||||
|
|
||||||
|
If there are only two colon (\fB:\fR) separated fields in a line, it means that
|
||||||
|
the user trusts a priori the integrity of the file whose pathname is \fIpath_of_the_executable_file\fR.
|
||||||
|
It can be, for example, a program in /bin or /usr/bin not modifiable by users.
|
||||||
|
|
||||||
|
If there are three fields (i.e. two colon characters), it means that the user wants the
|
||||||
|
cryptographic digest check on the executable file integrity.
|
||||||
|
When a user edits their scado file, if the field (\fIsha256_digest_of_the_executable\fR) is empty, \fBscado\fR
|
||||||
|
computes it automatically when the scado file is saved.
|
||||||
|
|
||||||
|
\fBScado\fR asks for user authentication by PAM to confirm any modification of the scado file.
|
||||||
|
|
||||||
|
There is also a \fITOCTTOU\fR protection at running time: \fDcado -S\fR copies the executable file
|
||||||
|
in a safe place, where the user cannot change it, and runs it only if the integrity check on it succeeds.
|
||||||
|
The user (or a malicious intruder acting as the user) cannot modify the file after the integrity check has completed
|
||||||
|
and before the program is loaded.
|
||||||
|
|
||||||
|
.SH OPTIONS
|
||||||
|
.I scado
|
||||||
|
accepts the following options:
|
||||||
|
.TP
|
||||||
|
\fB\-l
|
||||||
|
.TQ
|
||||||
|
\fB\-\-list
|
||||||
|
Display the current scado file. The actual file in the file system is not accessible by unprivileged users, for security reasons.
|
||||||
|
.TP
|
||||||
|
\fB\-e
|
||||||
|
.TQ
|
||||||
|
\fB\-\-edit
|
||||||
|
Edit the scado file of the current user using the editor specified by either the
|
||||||
|
\fBVISUAL\fR or the \fBEDITOR\fR environment variable (checked in that order).
|
||||||
|
After you exit from the editor, the modified file will be installed automatically.
|
||||||
|
.TP
|
||||||
|
\fB\-D
|
||||||
|
.TQ
|
||||||
|
\fB\-\-delete
|
||||||
|
Delete the current user's scado file.
|
||||||
|
.TP
|
||||||
|
\fB\-u\fR \fIcommand\fR
|
||||||
|
.TQ
|
||||||
|
\fB\-\-update\fR \fIcommand\fR
|
||||||
|
Recompute the hash of the line which starts with \fIcommand\fR.
|
||||||
|
.TP
|
||||||
|
\fB\-U
|
||||||
|
.TQ
|
||||||
|
\fB\-\-update-all
|
||||||
|
Update all the digest entries.
|
||||||
|
.TP
|
||||||
|
\fB\-h
|
||||||
|
.TQ
|
||||||
|
\fB\-\-help
|
||||||
|
print a short usage banner and exit.
|
||||||
|
|
||||||
|
.SH EXCEPTIONS FILES EXAMPLES
|
||||||
|
|
||||||
|
.PP
|
||||||
|
Allow \fBcado -S\fR to run /bin/ping providing it with the cap_net_raw capability, without any integrity check:
|
||||||
|
.RS 4
|
||||||
|
/bin/ping : cap_net_raw
|
||||||
|
.RE
|
||||||
|
|
||||||
|
.PP
|
||||||
|
Allow the activation of ping with cap_net_raw provided it has a specific SHA256 digest
|
||||||
|
.RS 4
|
||||||
|
/bin/ping : cap_net_raw : dcb237f1cb20ee7b1550900d1b524c554063fd17fc673c56d341736ced6bed4b
|
||||||
|
.RE
|
||||||
|
|
||||||
|
.PP
|
||||||
|
Compute the SAH256 digest of (the current version of) ping so,
|
||||||
|
allow the activation of ping with cap_net_raw provided it has not been modified.
|
||||||
|
.RS 4
|
||||||
|
/bin/ping : cap_net_raw :
|
||||||
|
.RE
|
||||||
|
|
||||||
|
.PP
|
||||||
|
If one of the example lines here above has been inserted in the user scado file using
|
||||||
|
\fBscado -e\fR, it is possible to execute ping as follows:
|
||||||
|
.RS 4
|
||||||
|
cado -S cap_net_raw /bin/ping
|
||||||
|
.RE
|
||||||
|
|
||||||
|
.SH SEE ALSO
|
||||||
|
\fBcado\fR(1),
|
||||||
|
\fBcapabilities\fR(7)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,377 @@
|
||||||
|
/*
|
||||||
|
* scado: setuid in capability sauce.
|
||||||
|
* Copyright (C) 2016 Davide Berardi, University of Bologna
|
||||||
|
*
|
||||||
|
* This file is part of cado.
|
||||||
|
*
|
||||||
|
* Cado is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <libgen.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
#include <pwd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
|
#include <file_utils.h>
|
||||||
|
#include <pam_check.h>
|
||||||
|
#include <get_scado_file.h>
|
||||||
|
|
||||||
|
#include <cado_paths.h>
|
||||||
|
#include <cado_const.h>
|
||||||
|
#include <scado_parse.h>
|
||||||
|
#include <compute_digest.h>
|
||||||
|
#include <config.h>
|
||||||
|
|
||||||
|
#define EDIT_PAM_MAXTRIES 3
|
||||||
|
|
||||||
|
#define SUCCESS 0
|
||||||
|
|
||||||
|
/* Generic error */
|
||||||
|
#define ERROR_EXIT -1
|
||||||
|
|
||||||
|
/* Authentication error */
|
||||||
|
#define ERROR_AUTH -2
|
||||||
|
|
||||||
|
const char *tmpdir;
|
||||||
|
|
||||||
|
#define OPTSTRING "hu:UDle"
|
||||||
|
struct option long_options[] = {
|
||||||
|
{"help", no_argument, NULL, 'h'},
|
||||||
|
{"update", required_argument, NULL, 'u'},
|
||||||
|
{"update-all", no_argument, NULL, 'U'},
|
||||||
|
{"delete", no_argument, NULL, 'D'},
|
||||||
|
{"list", no_argument, NULL, 'l'},
|
||||||
|
{"edit", no_argument, NULL, 'e'},
|
||||||
|
};
|
||||||
|
|
||||||
|
enum scado_option {none, update, delete, list, edit, toomany};
|
||||||
|
|
||||||
|
void usage_n_exit(char *progname, char *message) {
|
||||||
|
fprintf(stderr, "%s - script cado, setuid in capability sauce\n", progname);
|
||||||
|
fprintf(stderr, "usage: %s OPTIONS\n", progname);
|
||||||
|
fprintf(stderr, "Options:\n");
|
||||||
|
fprintf(stderr, " -h --help display help message and exit\n");
|
||||||
|
fprintf(stderr, " -u --update <command> update the checksum of command\n");
|
||||||
|
fprintf(stderr, " -U --update-all update the checksum of all commands\n");
|
||||||
|
fprintf(stderr, " -D --delete delete the scado file\n");
|
||||||
|
fprintf(stderr, " -l --list print the scado file\n");
|
||||||
|
fprintf(stderr, " -e --edit edit the scado file\n");
|
||||||
|
if (message)
|
||||||
|
fprintf(stderr, "\n%s\n",message);
|
||||||
|
exit(ERROR_EXIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* watchdog for garbage collection.
|
||||||
|
the temporary file for scado editing is removed in case of abend */
|
||||||
|
static int editor_garbage_collect(char *path) {
|
||||||
|
int checkpipe[2];
|
||||||
|
pid_t childpid;
|
||||||
|
if (pipe(checkpipe))
|
||||||
|
return -1;
|
||||||
|
/* Start garbage collector
|
||||||
|
* Double fork to avoid zombie processes and to remove the temporary file when it is not needed any more */
|
||||||
|
if(!(childpid = fork())) {
|
||||||
|
/* Child */
|
||||||
|
if(!fork()) {
|
||||||
|
char c = 0;
|
||||||
|
/* Grandchild */
|
||||||
|
if (close(checkpipe[1]) == 0 && setsid() > 0)
|
||||||
|
read(checkpipe[0], &c, 1);
|
||||||
|
if (c == 0)
|
||||||
|
unlink(path);
|
||||||
|
exit(0);
|
||||||
|
} else
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
waitpid(childpid, NULL, 0);
|
||||||
|
/* (grand) parent */
|
||||||
|
close(checkpipe[0]);
|
||||||
|
return(checkpipe[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void editor_garbage_collect_do_not_unlink(int fd) {
|
||||||
|
char c = 'K'; // keep it, any other non-null char would fit.
|
||||||
|
write(fd, &c, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* command line selectable functions */
|
||||||
|
|
||||||
|
int scado_none_or_toomany(char *progname, char *username, char *program_path) {
|
||||||
|
usage_n_exit(progname, "select exactly one option among: -u -U -D -l -e");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int scado_delete(char *progname, char *username, char *program_path) {
|
||||||
|
char scado_file[PATH_MAX];
|
||||||
|
|
||||||
|
if (get_scado_file(username, scado_file) < 0)
|
||||||
|
return ERROR_EXIT;
|
||||||
|
|
||||||
|
/* Get the authorization. */
|
||||||
|
if (pam_check(username) != PAM_SUCCESS) {
|
||||||
|
return ERROR_AUTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
return unlink(scado_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
int scado_update(char *progname, char *username, char *program_path) {
|
||||||
|
char tmp_file[PATH_MAX];
|
||||||
|
char scado_file[PATH_MAX];
|
||||||
|
|
||||||
|
if (get_scado_file(username, scado_file) < 0)
|
||||||
|
return ERROR_EXIT;
|
||||||
|
|
||||||
|
/* Get the authorization. */
|
||||||
|
if (pam_check(username) != PAM_SUCCESS) {
|
||||||
|
return ERROR_AUTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snprintf(tmp_file, PATH_MAX, "%s/%s", tmpdir, TMP_TEMPLATE) < 0)
|
||||||
|
return ERROR_EXIT;
|
||||||
|
|
||||||
|
if (copytemp(scado_file, tmp_file) < 0)
|
||||||
|
return ERROR_EXIT;
|
||||||
|
|
||||||
|
scado_copy_update(tmp_file, scado_file, program_path);
|
||||||
|
|
||||||
|
if (unlink(tmp_file) < 0)
|
||||||
|
return ERROR_EXIT;
|
||||||
|
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int scado_list(char *progname, char *username, char *program_path) {
|
||||||
|
char scado_file[PATH_MAX];
|
||||||
|
int fd;
|
||||||
|
int outfl = fcntl(STDOUT_FILENO, F_GETFL, 0);
|
||||||
|
if (outfl & O_APPEND)
|
||||||
|
fcntl(STDOUT_FILENO, F_SETFL, outfl & ~O_APPEND);
|
||||||
|
|
||||||
|
if (get_scado_file(username, scado_file) < 0)
|
||||||
|
return ERROR_EXIT;
|
||||||
|
|
||||||
|
if ((fd = open(scado_file, O_RDONLY)) < 0)
|
||||||
|
return fd;
|
||||||
|
|
||||||
|
copyfile(fd, STDOUT_FILENO);
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int scado_edit(char *progname, char *username, char *program_path) {
|
||||||
|
char tmp_file[PATH_MAX];
|
||||||
|
char scado_file[PATH_MAX];
|
||||||
|
char *editor;
|
||||||
|
char *argv[]={NULL, tmp_file, NULL};
|
||||||
|
int status = 0;
|
||||||
|
pid_t pid, xpid;
|
||||||
|
char digest_before[DIGESTSTRLEN + 1];
|
||||||
|
char digest_after[DIGESTSTRLEN + 1];
|
||||||
|
int garbage_collect_fd;
|
||||||
|
|
||||||
|
/* Ignore signals. */
|
||||||
|
(void) signal(SIGHUP, SIG_IGN);
|
||||||
|
(void) signal(SIGINT, SIG_IGN);
|
||||||
|
(void) signal(SIGQUIT, SIG_IGN);
|
||||||
|
|
||||||
|
if (get_scado_file(username, scado_file) < 0)
|
||||||
|
return ERROR_EXIT;
|
||||||
|
|
||||||
|
if (snprintf(tmp_file, PATH_MAX, "%s/%s", tmpdir, TMP_TEMPLATE) < 0)
|
||||||
|
return ERROR_EXIT;
|
||||||
|
|
||||||
|
if (copytemp_digest(scado_file, tmp_file, digest_before) < 0) {
|
||||||
|
*digest_before = 0;
|
||||||
|
if (errno == ENOENT) {
|
||||||
|
int tmpfd=mkstemp(tmp_file);
|
||||||
|
if (tmpfd < 0)
|
||||||
|
return ERROR_EXIT;
|
||||||
|
if (write(tmpfd, DEFAULT_MESSAGE, sizeof(DEFAULT_MESSAGE)-1) < 0)
|
||||||
|
return ERROR_EXIT;
|
||||||
|
close(tmpfd);
|
||||||
|
} else
|
||||||
|
return ERROR_EXIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
garbage_collect_fd = editor_garbage_collect(tmp_file);
|
||||||
|
|
||||||
|
/* Get the editor from the configuration. */
|
||||||
|
if (((editor = getenv("VISUAL")) == NULL || *editor == '\0') &&
|
||||||
|
((editor = getenv("EDITOR")) == NULL || *editor == '\0')) {
|
||||||
|
editor = EDITOR;
|
||||||
|
}
|
||||||
|
argv[0] = editor;
|
||||||
|
|
||||||
|
switch (pid = fork()) {
|
||||||
|
case -1:
|
||||||
|
perror("fork");
|
||||||
|
exit(ERROR_EXIT);
|
||||||
|
case 0:
|
||||||
|
/* XXX secure? */
|
||||||
|
if (setgid(getgid()) < 0) {
|
||||||
|
perror("setgid(getgid())");
|
||||||
|
exit(ERROR_EXIT);
|
||||||
|
}
|
||||||
|
if (setuid(getuid()) < 0) {
|
||||||
|
perror("setuid(getuid())");
|
||||||
|
exit(ERROR_EXIT);
|
||||||
|
}
|
||||||
|
execvp(argv[0], argv);
|
||||||
|
perror(editor);
|
||||||
|
exit(ERROR_EXIT);
|
||||||
|
default:
|
||||||
|
/* parent */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* parent */
|
||||||
|
for (;;) {
|
||||||
|
xpid = waitpid(pid, &status, 0);
|
||||||
|
if (xpid == -1) {
|
||||||
|
if(errno != EINTR) {
|
||||||
|
fprintf(stderr, "wait pid: error waiting for PID %ld (%s): %s \n",
|
||||||
|
(long) xpid, editor, strerror(errno));
|
||||||
|
return ERROR_EXIT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (WIFEXITED(status) && WEXITSTATUS(status)) {
|
||||||
|
fprintf(stderr,"wait pid: exit status");
|
||||||
|
return ERROR_EXIT;
|
||||||
|
}
|
||||||
|
else if(WIFSIGNALED(status)) {
|
||||||
|
fprintf(stderr, "%s killed: signal %d (%score dumped)\n",
|
||||||
|
editor, WTERMSIG(status), WCOREDUMP(status) ? "": "no ");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Restore signals. */
|
||||||
|
(void) signal(SIGHUP, SIG_DFL);
|
||||||
|
(void) signal(SIGINT, SIG_DFL);
|
||||||
|
(void) signal(SIGQUIT, SIG_DFL);
|
||||||
|
|
||||||
|
if(compute_digest(tmp_file, digest_after) < 0)
|
||||||
|
return ERROR_EXIT;
|
||||||
|
|
||||||
|
if (strcmp(digest_before, digest_after) != 0) {
|
||||||
|
int count;
|
||||||
|
|
||||||
|
/* Get the authorization. */
|
||||||
|
for (count = 0; count < EDIT_PAM_MAXTRIES; count++) {
|
||||||
|
if (pam_check(username) == PAM_SUCCESS) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (count < EDIT_PAM_MAXTRIES - 1)
|
||||||
|
fprintf(stderr, "PAM authorization failed\n");
|
||||||
|
else {
|
||||||
|
editor_garbage_collect_do_not_unlink(garbage_collect_fd);
|
||||||
|
fprintf(stderr, "A copy of the edited scado file has been saved as %s\n", tmp_file);
|
||||||
|
return ERROR_AUTH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scado_copy_update(tmp_file, scado_file, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
close(garbage_collect_fd);
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef int (* option_function) (char *progname, char *username, char *program_path);
|
||||||
|
|
||||||
|
/* array to select the requested option */
|
||||||
|
option_function option_map[] = {
|
||||||
|
scado_none_or_toomany,
|
||||||
|
scado_update,
|
||||||
|
scado_delete,
|
||||||
|
scado_list,
|
||||||
|
scado_edit,
|
||||||
|
scado_none_or_toomany};
|
||||||
|
|
||||||
|
/* update the chosen option. If one was already chosen, switch to "toomany" */
|
||||||
|
static inline enum scado_option set_option(enum scado_option option, enum scado_option newvalue) {
|
||||||
|
return option == none ? newvalue : toomany;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
struct passwd *pw;
|
||||||
|
char *progname = basename(argv[0]);
|
||||||
|
char *username;
|
||||||
|
char *program_path = ""; /* "" means ALL */
|
||||||
|
enum scado_option option=none;
|
||||||
|
|
||||||
|
int rval = 1;
|
||||||
|
|
||||||
|
if ((pw = getpwuid(getuid())) == NULL) {
|
||||||
|
fprintf(stderr, "Your UID isn't in the passwd file.\n");
|
||||||
|
exit(ERROR_EXIT);
|
||||||
|
}
|
||||||
|
username = pw->pw_name;
|
||||||
|
|
||||||
|
if ((tmpdir = getenv("TMPDIR")) == NULL || *tmpdir== '\0') {
|
||||||
|
tmpdir = TMPDIR;
|
||||||
|
}
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
int c = getopt_long(argc, argv, OPTSTRING, long_options, NULL);
|
||||||
|
if (c < 0)
|
||||||
|
break;
|
||||||
|
switch(c) {
|
||||||
|
case 'h':
|
||||||
|
usage_n_exit(progname, NULL);
|
||||||
|
break;
|
||||||
|
case 'u':
|
||||||
|
program_path = optarg;
|
||||||
|
case 'U':
|
||||||
|
option=set_option(option,update);
|
||||||
|
break;
|
||||||
|
case 'D':
|
||||||
|
option=set_option(option,delete);
|
||||||
|
break;
|
||||||
|
case 'l':
|
||||||
|
option=set_option(option,list);
|
||||||
|
break;
|
||||||
|
case 'e':
|
||||||
|
option=set_option(option,edit);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
usage_n_exit(progname, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (optind != argc) /* unknown trailing arguments */
|
||||||
|
usage_n_exit(progname, "unknown trailing arguments");
|
||||||
|
|
||||||
|
rval = option_map[option](progname, username, program_path);
|
||||||
|
|
||||||
|
if (rval == ERROR_AUTH) {
|
||||||
|
fprintf(stderr, "%s: Authorization failure.\n", progname);
|
||||||
|
} else if (rval) {
|
||||||
|
fprintf(stderr, "%s returned: %d, %s\n", progname, rval, strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
return rval;
|
||||||
|
}
|
|
@ -0,0 +1,315 @@
|
||||||
|
/*
|
||||||
|
* cado: execute a command in a capability ambient
|
||||||
|
* Copyright (C) 2016 Renzo Davoli and Davide Berardi, University of Bologna
|
||||||
|
*
|
||||||
|
* This file is part of cado.
|
||||||
|
*
|
||||||
|
* Cado is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <sys/uio.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <capset_from_namelist.h>
|
||||||
|
#include <compute_digest.h>
|
||||||
|
|
||||||
|
/* this structure stores the output of the "parsing" of a scado line.
|
||||||
|
a line has (at most) three colon ":" separated fields.
|
||||||
|
all field and end pointers are addresses within the string "line".
|
||||||
|
the i-th field begins at field[i] and terminates just before end[i] */
|
||||||
|
|
||||||
|
struct scado_line {
|
||||||
|
char *line;
|
||||||
|
char *field[3];
|
||||||
|
char *end[3];
|
||||||
|
};
|
||||||
|
|
||||||
|
/* parse a scado line */
|
||||||
|
static struct scado_line scado_parse(char *line) {
|
||||||
|
struct scado_line out={line, {NULL, NULL, NULL}, {NULL, NULL, NULL}};
|
||||||
|
char *scan=line;
|
||||||
|
int i;
|
||||||
|
for (i=0; i<3; i++) {
|
||||||
|
while (isspace(*scan)) scan++;
|
||||||
|
out.field[i] = scan;
|
||||||
|
if (*scan == '#' || *scan == 0) goto end;
|
||||||
|
while (*scan != ':') {
|
||||||
|
if (scan[0] == '\\' && scan[1] != 0) scan++;
|
||||||
|
if (*scan == '#' || *scan == 0) goto end;
|
||||||
|
scan++;
|
||||||
|
}
|
||||||
|
out.end[i] = scan;
|
||||||
|
scan++;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
end:
|
||||||
|
out.end[i] = scan;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* true if the current line matches path, false otherwise.
|
||||||
|
scado_path_match manages the escape chars '\' in the scado line
|
||||||
|
without any copy */
|
||||||
|
static int scado_path_match(const char *path, struct scado_line *line) {
|
||||||
|
char *lpath;
|
||||||
|
for (lpath = line->field[0]; *path && *lpath; path++, lpath++) {
|
||||||
|
if (lpath[0] == '\\' && lpath[1] != 0) lpath++;
|
||||||
|
if (*lpath != *path)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return (*path == 0 && (*lpath == 0 || isspace(*lpath) || *lpath == ':'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* get the capability set from a scado file line */
|
||||||
|
static int scado_get_caps(struct scado_line *line, uint64_t *capset) {
|
||||||
|
if (line->field[1]) {
|
||||||
|
size_t caplen = line->end[1] - line->field[1];
|
||||||
|
char caps[caplen+1];
|
||||||
|
strncpy(caps,line->field[1],caplen);
|
||||||
|
caps[caplen]=0;
|
||||||
|
return capset_from_namelist(caps, capset);
|
||||||
|
} else
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* scado_check_digest_syntax returns:
|
||||||
|
-1 if the digest field has syntax errors,
|
||||||
|
0 if it is an ampty field (but the field exists!)
|
||||||
|
1 if the line has just two fields (one ':') OR there is a well-formed hex digest */
|
||||||
|
|
||||||
|
static int scado_check_digest_syntax(struct scado_line *line) {
|
||||||
|
if (line->field[2]) {
|
||||||
|
int i;
|
||||||
|
size_t digestlen = line->end[2] - line->field[2];
|
||||||
|
char digest[digestlen+1];
|
||||||
|
strncpy(digest,line->field[2],digestlen);
|
||||||
|
digest[digestlen]=0;
|
||||||
|
if (*digest == 0)
|
||||||
|
return 0;
|
||||||
|
if (strlen(digest) < DIGESTSTRLEN)
|
||||||
|
return -1;
|
||||||
|
for (i = 0; i < DIGESTSTRLEN && isxdigit(digest[i]); i++)
|
||||||
|
;
|
||||||
|
if (i < DIGESTSTRLEN)
|
||||||
|
return -1;
|
||||||
|
for (; digest[i] != 0; i++)
|
||||||
|
if (!isspace(digest[i]))
|
||||||
|
return -1;
|
||||||
|
return 1;
|
||||||
|
} else
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* true if the field exists and it is not empty */
|
||||||
|
static inline int fieldok(struct scado_line *line, int i) {
|
||||||
|
return line->field[i] && line->end[i] != line->field[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* some syntax sanity checks */
|
||||||
|
static int scado_syntax_check(char *path, int lineno, struct scado_line *line) {
|
||||||
|
uint64_t capset;
|
||||||
|
int rv=0;
|
||||||
|
if (line->end[2] && line->end[2][0] == ':') {
|
||||||
|
fprintf(stderr,"%s line %d: extra trailing chars\n", path, lineno);
|
||||||
|
rv = -1;
|
||||||
|
}
|
||||||
|
if (fieldok(line, 0) && line->field[0][0] != '/') {
|
||||||
|
fprintf(stderr,"%s line %d: pathname is not absolute\n", path, lineno);
|
||||||
|
rv = -1;
|
||||||
|
}
|
||||||
|
if (fieldok(line, 0) && !fieldok(line, 1)) {
|
||||||
|
fprintf(stderr,"%s line %d: missing capability set\n", path, lineno);
|
||||||
|
rv = -1;
|
||||||
|
}
|
||||||
|
if (fieldok(line, 1) && scado_get_caps(line, &capset) < 0) {
|
||||||
|
fprintf(stderr,"%s line %d: wrong capability syntax\n", path, lineno);
|
||||||
|
rv = -1;
|
||||||
|
}
|
||||||
|
if (fieldok(line, 2) && scado_check_digest_syntax(line) < 0) {
|
||||||
|
fprintf(stderr,"%s line %d: wrong digest syntax\n", path, lineno);
|
||||||
|
rv = -1;
|
||||||
|
}
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* clean the path (parsing escape chars) in place */
|
||||||
|
static void cleanpath(char *path) {
|
||||||
|
char *out=path;
|
||||||
|
while(*path != 0 && !isspace(*path)) {
|
||||||
|
if (path[0] == '\\' && path[1] != 0) path++;
|
||||||
|
*out++ = *path++;
|
||||||
|
}
|
||||||
|
*out=0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* update a line */
|
||||||
|
/* inpath == NULL -> add the digest it if is missing
|
||||||
|
inpath == "" -> update the digest in any case
|
||||||
|
inpath == "/....." (a valid path) -> update the digest if the path of this line matches */
|
||||||
|
static void scado_update_line(FILE *fout, struct scado_line *line, char *inpath) {
|
||||||
|
if (line->field[2] != NULL) {
|
||||||
|
/* there is the digest field */
|
||||||
|
size_t pathlen = line->end[0] - line->field[0];
|
||||||
|
char path[pathlen+1];
|
||||||
|
/* create a temporary copy of the path on the stack and clean it*/
|
||||||
|
strncpy(path,line->field[0],pathlen);
|
||||||
|
path[pathlen]=0;
|
||||||
|
cleanpath(path);
|
||||||
|
int digestok=scado_check_digest_syntax(line);
|
||||||
|
if (digestok < 0) // do not update in case of syntax error
|
||||||
|
fprintf(fout, "%s\n", line->line);
|
||||||
|
else if (digestok == 0) { //missing digest -> fill in!
|
||||||
|
char digest[DIGESTSTRLEN + 1];
|
||||||
|
int len = line->field[2] - line->line;
|
||||||
|
if (compute_digest(path, digest) < 0)
|
||||||
|
fprintf(fout, "%s\n", line->line);
|
||||||
|
else if (line->end[2] != 0)
|
||||||
|
fprintf(fout,"%*.*s%s %s\n",len,len,line->line,digest,line->end[2]);
|
||||||
|
else
|
||||||
|
fprintf(fout,"%*.*s%s\n",len,len,line->line,digest);
|
||||||
|
} else if (inpath && (*inpath == 0 || strcmp(path, inpath) == 0)) { /* if ALL or this path */
|
||||||
|
char digest[DIGESTSTRLEN + 1];
|
||||||
|
int len = line->field[2] - line->line;
|
||||||
|
if (compute_digest(path, digest) < 0)
|
||||||
|
fprintf(fout, "%s\n", line->line);
|
||||||
|
else
|
||||||
|
fprintf(fout,"%*.*s%s%s\n",len,len,line->line,digest,line->field[2]+DIGESTSTRLEN);
|
||||||
|
} else
|
||||||
|
fprintf(fout, "%s\n", line->line);
|
||||||
|
} else
|
||||||
|
fprintf(fout, "%s\n", line->line);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* copy the file from inpath to outpath, updating the digest where required:
|
||||||
|
path == NULL -> add missing digests
|
||||||
|
path == "" -> all
|
||||||
|
*/
|
||||||
|
void scado_copy_update(char *inpath, char *outpath, char *path) {
|
||||||
|
FILE *fin = fopen(inpath, "r");
|
||||||
|
mode_t oldmask;
|
||||||
|
FILE *fout;
|
||||||
|
char *line;
|
||||||
|
size_t len=0;
|
||||||
|
int lineno=0;
|
||||||
|
ssize_t n;
|
||||||
|
if (fin == NULL)
|
||||||
|
return;
|
||||||
|
oldmask = umask(027);
|
||||||
|
fout = fopen(outpath, "w");
|
||||||
|
umask(oldmask);
|
||||||
|
if (fout == NULL) {
|
||||||
|
fclose(fin);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while ((n = getline(&line, &len, fin)) > 0) {
|
||||||
|
struct scado_line scado_line;
|
||||||
|
line[n-1] = 0;
|
||||||
|
scado_line = scado_parse(line);
|
||||||
|
lineno++;
|
||||||
|
scado_syntax_check(outpath, lineno, &scado_line);
|
||||||
|
scado_update_line(fout, &scado_line, path);
|
||||||
|
}
|
||||||
|
fclose(fin);
|
||||||
|
fclose(fout);
|
||||||
|
free(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* scan the scado file whose pathname is inpath seeking for the line matching with 'path'
|
||||||
|
scado_path_getinfo returns:
|
||||||
|
-1 in case of error, 0 if the line is missing, 1 if the line has been found */
|
||||||
|
/* if scado_path_getinfo succeeds, pcapset and digest are set to
|
||||||
|
the set of permitted capabilities and the hash digest, respectively. */
|
||||||
|
int scado_path_getinfo(char *inpath, const char *path, uint64_t *pcapset, char *digest) {
|
||||||
|
FILE *fin = fopen(inpath, "r");
|
||||||
|
char *line;
|
||||||
|
size_t len=0;
|
||||||
|
ssize_t n;
|
||||||
|
int lineno=0;
|
||||||
|
int rv = 0;
|
||||||
|
if (fin == NULL)
|
||||||
|
return -1;
|
||||||
|
while ((n = getline(&line, &len, fin)) > 0) {
|
||||||
|
struct scado_line scado_line;
|
||||||
|
line[n-1] = 0;
|
||||||
|
scado_line = scado_parse(line);
|
||||||
|
lineno++;
|
||||||
|
if (scado_path_match(path, &scado_line)) {
|
||||||
|
if (scado_syntax_check(inpath, lineno, &scado_line) == 0) {
|
||||||
|
if (scado_get_caps(&scado_line, pcapset) >= 0) {
|
||||||
|
if (scado_line.field[2]) {
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < DIGESTSTRLEN ; i++)
|
||||||
|
digest[i] = tolower(scado_line.field[2][i]);
|
||||||
|
digest[i] = 0;
|
||||||
|
rv = 1;
|
||||||
|
} else {
|
||||||
|
digest[0] = 0;
|
||||||
|
rv = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fclose(fin);
|
||||||
|
free(line);
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
int main() {
|
||||||
|
char *line;
|
||||||
|
size_t len=0;
|
||||||
|
struct scado_line s;
|
||||||
|
int lineno=0;
|
||||||
|
while (1) {
|
||||||
|
ssize_t n=getline(&line, &len, stdin);
|
||||||
|
line[n-1]=0;
|
||||||
|
lineno++;
|
||||||
|
s=scado_parse(line);
|
||||||
|
printf("L %s\n",s.line);
|
||||||
|
printf("P %s|%s\n",s.field[0],s.end[0]);
|
||||||
|
printf("C %s|%s\n",s.field[1],s.end[1]);
|
||||||
|
printf("H %s|%s\n",s.field[2],s.end[2]);
|
||||||
|
uint64_t cap=0;
|
||||||
|
printf("%d ",scado_get_caps(&s,&cap));
|
||||||
|
printf("%lx\n",cap);
|
||||||
|
scado_syntax_check(lineno, &s);
|
||||||
|
printf("NULL->");fflush(stdout);
|
||||||
|
scado_update_line(1, &s, NULL);
|
||||||
|
printf("ALL ->");fflush(stdout);
|
||||||
|
scado_update_line(1, &s, "");
|
||||||
|
printf("bash ->");fflush(stdout);
|
||||||
|
scado_update_line(1, &s, "/bin/bash");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if 0
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
scado_update(argv[1], argv[2], argv[3]);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if 0
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
char digest[DIGESTSTRLEN + 1];
|
||||||
|
uint64_t capset;
|
||||||
|
if (scado_path_getinfo(argv[1], argv[2], &capset, digest)) {
|
||||||
|
printf("%lx %s\n",capset,digest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -0,0 +1,17 @@
|
||||||
|
#ifndef _SCADO_PARSE_H
|
||||||
|
#define _SCADO_PARSE_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* copy file inpath to file outpath.
|
||||||
|
if path == NULL, add missing HASH digests
|
||||||
|
else if *path == 0, update all HASH digests
|
||||||
|
else update only the digest for path */
|
||||||
|
void scado_copy_update(char *inpath, char *outpath, char *path);
|
||||||
|
|
||||||
|
/* get info for file path.
|
||||||
|
if scado_path_getinfo returns 1 the path is authorized by scado,
|
||||||
|
pcapset and digest are the permitted set of capabilities and the digest respectively */
|
||||||
|
int scado_path_getinfo(char *inpath, const char *path, uint64_t *pcapset, char *digest);
|
||||||
|
|
||||||
|
#endif // _SCADO_PARSE.H
|
|
@ -36,6 +36,9 @@
|
||||||
#define PR_CAP_AMBIENT_LOWER 3
|
#define PR_CAP_AMBIENT_LOWER 3
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* set the ambient capabilities to match the bitmap capset.
|
||||||
|
the capability #k is active if and only if the (k+1)-th least significative bit in capset is 1.
|
||||||
|
(i.e. if and only if (capset & (1ULL << k)) is not zero. */
|
||||||
void set_ambient_cap(uint64_t capset)
|
void set_ambient_cap(uint64_t capset)
|
||||||
{
|
{
|
||||||
cap_value_t cap;
|
cap_value_t cap;
|
||||||
|
@ -59,6 +62,7 @@ void set_ambient_cap(uint64_t capset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* turn cap_dac_read_search on and off to have "extra" powers only when needed */
|
||||||
void raise_cap_dac_read_search(void) {
|
void raise_cap_dac_read_search(void) {
|
||||||
cap_value_t cap=CAP_DAC_READ_SEARCH;
|
cap_value_t cap=CAP_DAC_READ_SEARCH;
|
||||||
cap_t caps=cap_get_proc();
|
cap_t caps=cap_get_proc();
|
||||||
|
|
Loading…
Reference in New Issue