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 VAL_PS_DOTLOCK_USER (usually "root") in order to create a
5 *@ 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 - 2020 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
10 * SPDX-License-Identifier: ISC
12 * Permission to use, copy, modify, and/or distribute this software for any
13 * purpose with or without fee is hereby granted, provided that the above
14 * copyright notice and this permission notice appear in all copies.
16 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
17 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
19 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
20 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
21 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
22 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
25 #define su_FILE ps_dotlock_main
27 #define mx_SOURCE_PS_DOTLOCK_MAIN
29 #define su_ASSERT_EXPAND_NOTHING
36 #if defined mx_HAVE_PRCTL_DUMPABLE
37 # include <sys/prctl.h>
38 #elif defined mx_HAVE_PROCCTL_TRACE_CTL
39 # include <sys/procctl.h>
42 #elif defined mx_HAVE_PTRACE_DENY
43 # include <sys/ptrace.h>
44 #elif defined mx_HAVE_SETPFLAGS_PROTECT
48 #include "mx/file-locks.h"
51 #include "su/code-in.h"
53 /* TODO Avoid linkage errors, instantiate what is needed;
54 * TODO SU needs to be available as a library to overcome this,
55 * TODO or a compiler capable of inlining can only be used */
57 #ifdef su_MEM_ALLOC_DEBUG
58 boole
su__mem_check(su_DBG_LOC_ARGS_DECL_SOLE
) {return FAL0
;}
59 boole
su__mem_trace(su_DBG_LOC_ARGS_DECL_SOLE
) {return FAL0
;}
61 #define su_err_no() errno
62 #define su_err_set_no(X) (errno = X)
64 static void _ign_signal(int signum
);
65 static uz
n_msleep(uz millis
, boole ignint
);
67 #include "mx/file-dotlock.h" /* $(PS_DOTLOCK_SRCDIR) */
70 _ign_signal(int signum
){
71 struct sigaction nact
, oact
;
73 nact
.sa_handler
= SIG_IGN
;
74 sigemptyset(&nact
.sa_mask
);
76 sigaction(signum
, &nact
, &oact
);
80 n_msleep(uz millis
, boole ignint
){
83 #ifdef mx_HAVE_NANOSLEEP
85 struct timespec ts
, trem
;
88 ts
.tv_sec
= millis
/ 1000;
89 ts
.tv_nsec
= (millis
%= 1000) * 1000 * 1000;
91 while((i
= nanosleep(&ts
, &trem
)) != 0 && ignint
)
94 : (trem
.tv_sec
* 1000) + (trem
.tv_nsec
/ (1000 * 1000));
97 #elif defined mx_HAVE_SLEEP
98 if((millis
/= 1000) == 0)
100 while((rv
= sleep(S(ui
,millis
))) != 0 && ignint
)
103 # error Configuration should have detected a function for sleeping.
109 main(int argc
, char **argv
){
111 struct mx_file_dotlock_info fdi
;
114 enum mx_file_dotlock_state fdls
;
116 /* We're a dumb helper, ensure as much as we can no one else uses us */
118 strcmp(argv
[ 0], VAL_PS_DOTLOCK
) ||
119 (argv
[1][0] != 'r' && argv
[1][0] != 'w') ||
120 strcmp(argv
[ 1] + 1, "dotlock") ||
121 strcmp(argv
[ 2], "mailbox") ||
122 strchr(argv
[ 3], '/') != NULL
/* Seal path injection.. */ ||
123 strcmp(argv
[ 4], "name") ||
124 strcmp(argv
[ 6], "hostname") ||
125 strcmp(argv
[ 8], "randstr") ||
126 strchr(argv
[ 9], '/') != NULL
/* ..attack vector */ ||
127 strcmp(argv
[10], "pollmsecs") ||
128 fstat(STDIN_FILENO
, &stb
) == -1 || !S_ISFIFO(stb
.st_mode
) ||
129 fstat(STDOUT_FILENO
, &stb
) == -1 || !S_ISFIFO(stb
.st_mode
)){
132 "This is a helper program of " VAL_BINDIR
"/" VAL_UAGENT
".\n"
133 " It is capable of gaining more privileges than " VAL_UAGENT
"\n"
134 " and will be used to create lock files.\n"
135 " The sole purpose is outsourcing of high privileges into\n"
136 " fewest lines of code in order to reduce attack surface.\n"
137 " This program cannot be run by itself.\n");
140 /* Prevent one more path injection attack vector, but be friendly */
145 for(ccp
= argv
[7], cp
= hostbuf
, i
= 0; (c
= *ccp
) != '\0'; ++cp
, ++ccp
){
146 *cp
= (c
== '/' ? '_' : c
);
147 if(++i
== sizeof(hostbuf
) -1)
156 fdi
.fdi_file_name
= argv
[3];
157 fdi
.fdi_lock_name
= argv
[5];
158 fdi
.fdi_hostname
= argv
[7];
159 fdi
.fdi_randstr
= argv
[9];
160 fdi
.fdi_pollmsecs
= S(uz
,strtoul(argv
[11], NULL
, 10)); /* XXX SU */
162 /* Ensure the lock name and the file name are identical */
166 i
= strlen(fdi
.fdi_file_name
);
167 if(i
== 0 || strncmp(fdi
.fdi_file_name
, fdi
.fdi_lock_name
, i
) ||
168 fdi
.fdi_lock_name
[i
] == '\0' ||
169 strcmp(fdi
.fdi_lock_name
+ i
, ".lock"))
173 /* Ensure that we got some random string, and some hostname.
174 * a_dotlock_create() will later ensure that it will produce some string
175 * not-equal to .fdi_lock_name if it is called by us */
176 if(fdi
.fdi_hostname
[0] == '\0' || fdi
.fdi_randstr
[0] == '\0')
179 close(STDERR_FILENO
);
181 /* In order to prevent stale lock files at all cost block any signals until
182 * we have unlinked the lock file.
183 * It is still not safe because we may be SIGKILLed and may linger around
184 * because we have been SIGSTOPped, but unfortunately the standard doesn't
185 * give any option, e.g. atcrash() or open(O_TEMPORARY_KEEP_NAME) or so, ---
186 * and then again we should not unlink(2) the lock file unless our parent
187 * has finalized the synchronization! While at it, let me rant about the
188 * default action of realtime signals, program termination */
189 _ign_signal(SIGPIPE
); /* (Inherited, though) */
191 sigdelset(&nset
, SIGCONT
); /* (Rather redundant, though) */
192 sigprocmask(SIG_BLOCK
, &nset
, &oset
);
194 fdls
= mx_FILE_DOTLOCK_STATE_NOPERM
| mx_FILE_DOTLOCK_STATE_ABANDON
;
196 /* First of all: we only dotlock when the executing user has the necessary
197 * rights to access the mailbox */
198 if(access(fdi
.fdi_file_name
, (argv
[1][0] == 'r' ? R_OK
: R_OK
| W_OK
)))
201 /* We need UID and GID information about the mailbox to lock */
202 if(stat(fdi
.fdi_file_name
, fdi
.fdi_stb
= &stb
) == -1)
205 fdls
= mx_FILE_DOTLOCK_STATE_PRIVFAILED
| mx_FILE_DOTLOCK_STATE_ABANDON
;
207 /* We are SETUID and do not want to become traced or being attached to */
208 #if defined mx_HAVE_PRCTL_DUMPABLE
209 if(prctl(PR_SET_DUMPABLE
, 0))
211 #elif defined mx_HAVE_PROCCTL_TRACE_CTL
213 int disable_trace
= PROC_TRACE_CTL_DISABLE
;
215 if(procctl(P_PID
, getpid(), PROC_TRACE_CTL
, &disable_trace
) == -1)
218 #elif defined mx_HAVE_PTRACE_DENY
219 if(ptrace(PT_DENY_ATTACH
, 0, 0, 0) == -1)
221 #elif defined mx_HAVE_SETPFLAGS_PROTECT
222 if(setpflags(__PROC_PROTECT
, 1))
226 /* This helper is only executed when really needed, it thus doesn't make
227 * sense to try to continue with initial privileges */
228 if(setuid(geteuid()) || setgid(getegid()))
231 fdls
= a_file_lock_dotlock_create(&fdi
);
233 /* Finally: notify our parent about the actual lock state.. */
235 write(STDOUT_FILENO
, &fdls
, sizeof fdls
);
236 close(STDOUT_FILENO
);
238 /* ..then eventually wait until we shall remove the lock again, which will
239 * be notified via the read returning */
240 if(fdls
== mx_FILE_DOTLOCK_STATE_NONE
){
241 read(STDIN_FILENO
, &fdls
, sizeof fdls
);
243 unlink(fdi
.fdi_lock_name
);
246 sigprocmask(SIG_SETMASK
, &oset
, NULL
);
247 return (fdls
== mx_FILE_DOTLOCK_STATE_NONE
? n_EXIT_OK
: n_EXIT_ERR
);
250 #include "su/code-ou.h"