comparison svr-main.c @ 1861:2b3a8026a6ce

Add re-exec for server This allows ASLR to re-randomize the address space for every connection, preventing some vulnerabilities from being exploitable by repeated probing. Overhead (memory and time) is yet to be confirmed. At present this is only enabled on Linux. Other BSD platforms with fexecve() would probably also work though have not been tested.
author Matt Johnston <matt@ucc.asn.au>
date Sun, 30 Jan 2022 10:14:56 +0800
parents 6ea18ca8fc03
children adfcdfb161a4
comparison
equal deleted inserted replaced
1860:5001e9c5641f 1861:2b3a8026a6ce
33 33
34 static size_t listensockets(int *sock, size_t sockcount, int *maxfd); 34 static size_t listensockets(int *sock, size_t sockcount, int *maxfd);
35 static void sigchld_handler(int dummy); 35 static void sigchld_handler(int dummy);
36 static void sigsegv_handler(int); 36 static void sigsegv_handler(int);
37 static void sigintterm_handler(int fish); 37 static void sigintterm_handler(int fish);
38 #if INETD_MODE
39 static void main_inetd(void); 38 static void main_inetd(void);
40 #endif 39 static void main_noinetd(int argc, char ** argv);
41 #if NON_INETD_MODE
42 static void main_noinetd(void);
43 #endif
44 static void commonsetup(void); 40 static void commonsetup(void);
45 41
46 #if defined(DBMULTI_dropbear) || !DROPBEAR_MULTI 42 #if defined(DBMULTI_dropbear) || !DROPBEAR_MULTI
47 #if defined(DBMULTI_dropbear) && DROPBEAR_MULTI 43 #if defined(DBMULTI_dropbear) && DROPBEAR_MULTI
48 int dropbear_main(int argc, char ** argv) 44 int dropbear_main(int argc, char ** argv)
53 _dropbear_exit = svr_dropbear_exit; 49 _dropbear_exit = svr_dropbear_exit;
54 _dropbear_log = svr_dropbear_log; 50 _dropbear_log = svr_dropbear_log;
55 51
56 disallow_core(); 52 disallow_core();
57 53
54 if (argc < 1) {
55 dropbear_exit("Bad argc");
56 }
57
58 /* get commandline options */ 58 /* get commandline options */
59 svr_getopts(argc, argv); 59 svr_getopts(argc, argv);
60 60
61 #if INETD_MODE 61 #if INETD_MODE
62 /* service program mode */ 62 /* service program mode */
64 main_inetd(); 64 main_inetd();
65 /* notreached */ 65 /* notreached */
66 } 66 }
67 #endif 67 #endif
68 68
69 #if DROPBEAR_DO_REEXEC
70 if (svr_opts.reexec_child) {
71 #ifdef PR_SET_NAME
72 /* Fix the "Name:" in /proc/pid/status, otherwise it's
73 a FD number from fexecve.
74 Failure doesn't really matter, it's mostly aesthetic */
75 prctl(PR_SET_NAME, basename(argv[0]), 0, 0);
76 #endif
77 main_inetd();
78 /* notreached */
79 }
80 #endif
81
69 #if NON_INETD_MODE 82 #if NON_INETD_MODE
70 main_noinetd(); 83 main_noinetd(argc, argv);
71 /* notreached */ 84 /* notreached */
72 #endif 85 #endif
73 86
74 dropbear_exit("Compiled without normal mode, can't run without -i\n"); 87 dropbear_exit("Compiled without normal mode, can't run without -i\n");
75 return -1; 88 return -1;
76 } 89 }
77 #endif 90 #endif
78 91
79 #if INETD_MODE 92 #if INETD_MODE || DROPBEAR_DO_REEXEC
80 static void main_inetd() { 93 static void main_inetd() {
81 char *host, *port = NULL; 94 char *host, *port = NULL;
82 95
83 /* Set up handlers, syslog */ 96 /* Set up handlers, syslog */
84 commonsetup(); 97 commonsetup();
85 98
86 seedrandom(); 99 seedrandom();
87 100
88 #if DEBUG_TRACE 101 if (!svr_opts.reexec_child) {
89 if (debug_trace) { 102 /* In case our inetd was lax in logging source addresses */
90 /* -v output goes to stderr which would get sent over the inetd network socket */ 103 get_socket_address(0, NULL, NULL, &host, &port, 0);
91 dropbear_exit("Dropbear inetd mode is incompatible with debug -v"); 104 dropbear_log(LOG_INFO, "Child connection from %s:%s", host, port);
92 } 105 m_free(host);
93 #endif 106 m_free(port);
94 107
95 /* In case our inetd was lax in logging source addresses */ 108 /* Don't check the return value - it may just fail since inetd has
96 get_socket_address(0, NULL, NULL, &host, &port, 0); 109 * already done setsid() after forking (xinetd on Darwin appears to do
97 dropbear_log(LOG_INFO, "Child connection from %s:%s", host, port); 110 * this */
98 m_free(host); 111 setsid();
99 m_free(port); 112 }
100
101 /* Don't check the return value - it may just fail since inetd has
102 * already done setsid() after forking (xinetd on Darwin appears to do
103 * this */
104 setsid();
105 113
106 /* Start service program 114 /* Start service program
107 * -1 is a dummy childpipe, just something we can close() without 115 * -1 is a dummy childpipe, just something we can close() without
108 * mattering. */ 116 * mattering. */
109 svr_session(0, -1); 117 svr_session(0, -1);
111 /* notreached */ 119 /* notreached */
112 } 120 }
113 #endif /* INETD_MODE */ 121 #endif /* INETD_MODE */
114 122
115 #if NON_INETD_MODE 123 #if NON_INETD_MODE
116 static void main_noinetd() { 124 static void main_noinetd(int argc, char ** argv) {
117 fd_set fds; 125 fd_set fds;
118 unsigned int i, j; 126 unsigned int i, j;
119 int val; 127 int val;
120 int maxsock = -1; 128 int maxsock = -1;
121 int listensocks[MAX_LISTEN_ADDR]; 129 int listensocks[MAX_LISTEN_ADDR];
122 size_t listensockcount = 0; 130 size_t listensockcount = 0;
123 FILE *pidfile = NULL; 131 FILE *pidfile = NULL;
132 int execfd = -1;
124 133
125 int childpipes[MAX_UNAUTH_CLIENTS]; 134 int childpipes[MAX_UNAUTH_CLIENTS];
126 char * preauth_addrs[MAX_UNAUTH_CLIENTS]; 135 char * preauth_addrs[MAX_UNAUTH_CLIENTS];
127 136
128 int childsock; 137 int childsock;
129 int childpipe[2]; 138 int childpipe[2];
139
140 (void)argc;
141 (void)argv;
130 142
131 /* Note: commonsetup() must happen before we daemon()ise. Otherwise 143 /* Note: commonsetup() must happen before we daemon()ise. Otherwise
132 daemon() will chdir("/"), and we won't be able to find local-dir 144 daemon() will chdir("/"), and we won't be able to find local-dir
133 hostkeys. */ 145 hostkeys. */
134 commonsetup(); 146 commonsetup();
136 /* sockets to identify pre-authenticated clients */ 148 /* sockets to identify pre-authenticated clients */
137 for (i = 0; i < MAX_UNAUTH_CLIENTS; i++) { 149 for (i = 0; i < MAX_UNAUTH_CLIENTS; i++) {
138 childpipes[i] = -1; 150 childpipes[i] = -1;
139 } 151 }
140 memset(preauth_addrs, 0x0, sizeof(preauth_addrs)); 152 memset(preauth_addrs, 0x0, sizeof(preauth_addrs));
141 153
142 /* Set up the listening sockets */ 154 /* Set up the listening sockets */
143 listensockcount = listensockets(listensocks, MAX_LISTEN_ADDR, &maxsock); 155 listensockcount = listensockets(listensocks, MAX_LISTEN_ADDR, &maxsock);
144 if (listensockcount == 0) 156 if (listensockcount == 0)
145 { 157 {
146 dropbear_exit("No listening ports available."); 158 dropbear_exit("No listening ports available.");
147 } 159 }
148 160
149 for (i = 0; i < listensockcount; i++) { 161 for (i = 0; i < listensockcount; i++) {
150 FD_SET(listensocks[i], &fds); 162 FD_SET(listensocks[i], &fds);
151 } 163 }
164
165 #if DROPBEAR_DO_REEXEC
166 execfd = open(argv[0], O_CLOEXEC|O_RDONLY);
167 if (execfd < 0) {
168 /* Just fallback to straight fork */
169 TRACE(("Couldn't open own binary %s, disabling re-exec: %s", argv[0], strerror(errno)))
170 }
171 #endif
152 172
153 /* fork */ 173 /* fork */
154 if (svr_opts.forkbg) { 174 if (svr_opts.forkbg) {
155 int closefds = 0; 175 int closefds = 0;
156 #if !DEBUG_TRACE 176 #if !DEBUG_TRACE
179 199
180 /* incoming connection select loop */ 200 /* incoming connection select loop */
181 for(;;) { 201 for(;;) {
182 202
183 DROPBEAR_FD_ZERO(&fds); 203 DROPBEAR_FD_ZERO(&fds);
184 204
185 /* listening sockets */ 205 /* listening sockets */
186 for (i = 0; i < listensockcount; i++) { 206 for (i = 0; i < listensockcount; i++) {
187 FD_SET(listensocks[i], &fds); 207 FD_SET(listensocks[i], &fds);
188 } 208 }
189 209
199 219
200 if (ses.exitflag) { 220 if (ses.exitflag) {
201 unlink(svr_opts.pidfile); 221 unlink(svr_opts.pidfile);
202 dropbear_exit("Terminated by signal"); 222 dropbear_exit("Terminated by signal");
203 } 223 }
204 224
205 if (val == 0) { 225 if (val == 0) {
206 /* timeout reached - shouldn't happen. eh */ 226 /* timeout reached - shouldn't happen. eh */
207 continue; 227 continue;
208 } 228 }
209 229
284 dropbear_log(LOG_WARNING, "Error forking: %s", strerror(errno)); 304 dropbear_log(LOG_WARNING, "Error forking: %s", strerror(errno));
285 goto out; 305 goto out;
286 } 306 }
287 307
288 addrandom((void*)&fork_ret, sizeof(fork_ret)); 308 addrandom((void*)&fork_ret, sizeof(fork_ret));
289 309
290 if (fork_ret > 0) { 310 if (fork_ret > 0) {
291 311
292 /* parent */ 312 /* parent */
293 childpipes[conn_idx] = childpipe[0]; 313 childpipes[conn_idx] = childpipe[0];
294 m_close(childpipe[1]); 314 m_close(childpipe[1]);
313 for (j = 0; j < listensockcount; j++) { 333 for (j = 0; j < listensockcount; j++) {
314 m_close(listensocks[j]); 334 m_close(listensocks[j]);
315 } 335 }
316 336
317 m_close(childpipe[0]); 337 m_close(childpipe[0]);
338
339 if (execfd >= 0) {
340 #if DROPBEAR_DO_REEXEC
341 /* Add "-2" to the args and re-execute ourself */
342 char **new_argv = m_malloc(sizeof(char*) * (argc+1));
343 memcpy(new_argv, argv, sizeof(char*) * argc);
344 new_argv[argc] = "-2";
345
346 if ((dup2(childsock, STDIN_FILENO) < 0)) {
347 dropbear_exit("dup2 failed: %s", strerror(errno));
348 }
349 m_close(childsock);
350 /* Re-execute ourself */
351 fexecve(execfd, new_argv, environ);
352 /* Not reached on success */
353
354 /* Fall back on plain fork otherwise */
355 TRACE(("fexecve failed, disabling re-exec: %s", strerror(errno)))
356 m_free(new_argv);
357 #endif /* DROPBEAR_DO_REEXEC */
358 }
318 359
319 /* start the session */ 360 /* start the session */
320 svr_session(childsock, childpipe[1]); 361 svr_session(childsock, childpipe[1]);
321 /* don't return */ 362 /* don't return */
322 dropbear_assert(0); 363 dropbear_assert(0);