new features: scado and conditions in cado.conf

This commit is contained in:
Renzo Davoli 2016-08-09 17:46:05 +02:00
parent 648d301eb3
commit bfe686a1c5
24 changed files with 1507 additions and 28 deletions

View File

@ -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
View File

@ -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)

72
cado.c
View File

@ -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);
} }

39
cado_const.h Normal file
View File

@ -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 */

11
cado_paths.h Normal file
View File

@ -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 */

213
cado_scado_check.c Normal file
View File

@ -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;
}
}

6
cado_scado_check.h Normal file
View File

@ -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

View File

@ -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;
} }

View File

@ -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;
for (; (onecap=strtok_r(namelist,",",&tmptok)) != NULL; namelist=NULL) char *spacetok;
rv |= addcap(onecap,capset); *capset = 0;
for (; (onecap = strtok_r(namelist,",",&tmptok)) != NULL; namelist = NULL)
rv |= addcap(strtok_r(onecap," \t",&spacetok), capset);
return rv; return rv;
} }

92
compute_digest.c Normal file
View File

@ -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;
}

20
compute_digest.h Normal file
View File

@ -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 */

View File

@ -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])

75
file_utils.c Normal file
View File

@ -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;
}

13
file_utils.h Normal file
View File

@ -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

11
get_scado_file.h Normal file
View File

@ -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 */

View File

@ -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];

View File

@ -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);

View File

@ -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

View File

@ -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) {

140
scado.1 Normal file
View File

@ -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)

377
scado.c Normal file
View File

@ -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;
}

315
scado_parse.c Normal file
View File

@ -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

17
scado_parse.h Normal file
View File

@ -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

View File

@ -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();