1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Privilege-separated dot file lock program (OPT_DOTLOCK=yes)
3 *@ that is capable of calling setuid(2) and change its user identity
4 *@ to the configured VAL_PRIVSEP_USER (usually "root"), in order to create
5 *@ a dotlock file with the same UID/GID as the mailbox to be locked.
6 *@ It should be started when chdir(2)d to the lock file's directory,
7 *@ with a symlink-resolved target and with SIGPIPE being ignored.
9 * Copyright (c) 2015 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
11 * Permission to use, copy, modify, and/or distribute this software for any
12 * purpose with or without fee is hereby granted, provided that the above
13 * copyright notice and this permission notice appear in all copies.
15 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
16 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
17 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
18 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
20 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
21 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
24 #define n_FILE privsep
25 #define n_PRIVSEP_SOURCE
29 #if defined HAVE_PRCTL_DUMPABLE
30 # include <sys/prctl.h>
31 #elif defined HAVE_PTRACE_DENY
32 # include <sys/ptrace.h>
33 #elif defined HAVE_SETPFLAGS_PROTECT
37 static void _ign_signal(int signum
);
38 static uiz_t
n_msleep(uiz_t millis
, bool_t ignint
);
43 _ign_signal(int signum
){
44 struct sigaction nact
, oact
;
46 nact
.sa_handler
= SIG_IGN
;
47 sigemptyset(&nact
.sa_mask
);
49 sigaction(signum
, &nact
, &oact
);
53 n_msleep(uiz_t millis
, bool_t ignint
){
58 struct timespec ts
, trem
;
61 ts
.tv_sec
= millis
/ 1000;
62 ts
.tv_nsec
= (millis
%= 1000) * 1000 * 1000;
64 while((i
= nanosleep(&ts
, &trem
)) != 0 && ignint
)
66 rv
= (i
== 0) ? 0 : (trem
.tv_sec
* 1000) + (trem
.tv_nsec
/ (1000 * 1000));
69 #elif defined HAVE_SLEEP
70 if((millis
/= 1000) == 0)
72 while((rv
= sleep((unsigned int)millis
)) != 0 && ignint
)
75 # error Configuration should have detected a function for sleeping.
81 main(int argc
, char **argv
){
83 struct n_dotlock_info di
;
86 enum n_dotlock_state dls
;
88 /* We're a dumb helper, ensure as much as we can noone else uses us */
90 strcmp(argv
[ 0], VAL_PRIVSEP
) ||
91 (argv
[1][0] != 'r' && argv
[1][0] != 'w') ||
92 strcmp(argv
[ 1] + 1, "dotlock") ||
93 strcmp(argv
[ 2], "mailbox") ||
94 strchr(argv
[ 3], '/') != NULL
/* Seal path injection.. */ ||
95 strcmp(argv
[ 4], "name") ||
96 strcmp(argv
[ 6], "hostname") ||
97 strcmp(argv
[ 8], "randstr") ||
98 strchr(argv
[ 9], '/') != NULL
/* ..attack vector */ ||
99 strcmp(argv
[10], "pollmsecs") ||
100 fstat(STDIN_FILENO
, &stb
) == -1 || !S_ISFIFO(stb
.st_mode
) ||
101 fstat(STDOUT_FILENO
, &stb
) == -1 || !S_ISFIFO(stb
.st_mode
)){
104 "This is a helper program of " VAL_UAGENT
" (in " VAL_BINDIR
").\n"
105 " It is capable of gaining more privileges than " VAL_UAGENT
"\n"
106 " and will be used to create lock files.\n"
107 " The sole purpose is outsourcing of high privileges into\n"
108 " fewest lines of code in order to reduce attack surface.\n"
109 " This program cannot be run by itself.\n");
112 /* Prevent one more path injection attack vector, but be friendly */
117 for(ccp
= argv
[7], cp
= hostbuf
, i
= 0; (c
= *ccp
) != '\0'; ++cp
, ++ccp
){
118 *cp
= (c
== '/' ? '_' : c
);
119 if(++i
== sizeof(hostbuf
) -1)
128 di
.di_file_name
= argv
[3];
129 di
.di_lock_name
= argv
[5];
130 di
.di_hostname
= argv
[7];
131 di
.di_randstr
= argv
[9];
132 di
.di_pollmsecs
= (size_t)strtoul(argv
[11], NULL
, 10);
134 /* Ensure the lock name and the file name are identical */
136 size_t i
= strlen(di
.di_file_name
);
138 if(i
== 0 || strncmp(di
.di_file_name
, di
.di_lock_name
, i
) ||
139 di
.di_lock_name
[i
] == '\0' || strcmp(di
.di_lock_name
+ i
, ".lock"))
143 close(STDERR_FILENO
);
145 /* In order to prevent stale lock files at all cost block any signals until
146 * we have unlinked the lock file.
147 * It is still not safe because we may be SIGKILLed and may linger around
148 * because we have been SIGSTOPped, but unfortunately the standard doesn't
149 * give any option, e.g. atcrash() or open(O_TEMPORARY_KEEP_NAME) or so, ---
150 * and then again we should not unlink(2) the lock file unless our parent
151 * has finalized the synchronization! While at it, let me rant about the
152 * default action of realtime signals, program termination */
153 _ign_signal(SIGPIPE
); /* (Inherited, though) */
155 sigdelset(&nset
, SIGCONT
); /* (Rather redundant, though) */
156 sigprocmask(SIG_BLOCK
, &nset
, &oset
);
158 dls
= n_DLS_NOPERM
| n_DLS_ABANDON
;
160 /* First of all: we only dotlock when the executing user has the necessary
161 * rights to access the mailbox */
162 if(access(di
.di_file_name
, (argv
[1][0] == 'r' ? R_OK
: R_OK
| W_OK
)))
165 /* We need UID and GID information about the mailbox to lock */
166 if(stat(di
.di_file_name
, di
.di_stb
= &stb
) == -1)
169 dls
= n_DLS_PRIVFAILED
| n_DLS_ABANDON
;
171 /* We are SETUID and do not want to become traced or being attached to */
172 #if defined HAVE_PRCTL_DUMPABLE
173 if(prctl(PR_SET_DUMPABLE
, 0))
175 #elif defined HAVE_PTRACE_DENY
176 if(ptrace(PT_DENY_ATTACH
, 0, 0, 0) == -1)
178 #elif defined HAVE_SETPFLAGS_PROTECT
179 if(setpflags(__PROC_PROTECT
, 1))
183 /* This privsep helper only gets executed when needed, it thus doesn't make
184 * sense to try to continue with initial privileges */
185 if(setuid(geteuid()))
188 dls
= a_dotlock_create(&di
);
190 /* Finally: notify our parent about the actual lock state.. */
192 write(STDOUT_FILENO
, &dls
, sizeof dls
);
193 close(STDOUT_FILENO
);
195 /* ..then eventually wait until we shall remove the lock again, which will
196 * be notified via the read returning */
197 if(dls
== n_DLS_NONE
){
198 read(STDIN_FILENO
, &dls
, sizeof dls
);
200 unlink(di
.di_lock_name
);
203 sigprocmask(SIG_SETMASK
, &oset
, NULL
);
204 return (dls
== n_DLS_NONE
? n_EXIT_OK
: n_EXIT_ERR
);