Fossil SCM
Avoid another attack vector when using SSH sync protocol by not calling a shell interpreter. Fixes only Unix-like environments by using execvp() instead of a string that can be mishandled by /bin/sh.
Commit
ce7baa9798de21aabfea96f80e0f61597eda7e989bc98b7a7df8cd23d63cdff9
Parent
45a3d4b1670f123…
2 files changed
+24
-22
+18
-6
+24
-22
| --- src/http_transport.c | ||
| +++ src/http_transport.c | ||
| @@ -103,53 +103,55 @@ | ||
| 103 | 103 | */ |
| 104 | 104 | int transport_ssh_open(UrlData *pUrlData){ |
| 105 | 105 | /* For SSH we need to create and run SSH fossil http |
| 106 | 106 | ** to talk to the remote machine. |
| 107 | 107 | */ |
| 108 | - char *zSsh; /* The base SSH command */ | |
| 109 | - Blob zCmd; /* The SSH command */ | |
| 108 | + Blob sshBase; /* The base SSH command */ | |
| 109 | + Blob aArgv[50]; /* The SSH arguments array */ | |
| 110 | 110 | char *zHost; /* The host name to contact */ |
| 111 | 111 | int n; /* Size of prefix string */ |
| 112 | + int nToken, i; | |
| 112 | 113 | |
| 113 | 114 | socket_ssh_resolve_addr(pUrlData); |
| 114 | - zSsh = db_get("ssh-command", zDefaultSshCmd); | |
| 115 | - blob_init(&zCmd, zSsh, -1); | |
| 115 | + blob_init(&sshBase, db_get("ssh-command", zDefaultSshCmd), -1); | |
| 116 | + blobarray_zero(aArgv, count(aArgv)); | |
| 117 | + n = nToken = blob_tokenize(&sshBase, aArgv, count(aArgv)); | |
| 116 | 118 | if( pUrlData->port!=pUrlData->dfltPort && pUrlData->port ){ |
| 117 | 119 | #ifdef _WIN32 |
| 118 | - blob_appendf(&zCmd, " -P %d", pUrlData->port); | |
| 120 | + blob_append(&aArgv[nToken++], "-P", 2); | |
| 119 | 121 | #else |
| 120 | - blob_appendf(&zCmd, " -p %d", pUrlData->port); | |
| 122 | + blob_append(&aArgv[nToken++], "-p", 2); | |
| 121 | 123 | #endif |
| 122 | - } | |
| 123 | - if( g.fSshTrace ){ | |
| 124 | - fossil_force_newline(); | |
| 125 | - fossil_print("%s", blob_str(&zCmd)); /* Show the base of the SSH command */ | |
| 124 | + blob_appendf(&aArgv[nToken++], "%d", pUrlData->port); | |
| 126 | 125 | } |
| 127 | 126 | if( pUrlData->user && pUrlData->user[0] ){ |
| 128 | 127 | zHost = mprintf("%s@%s", pUrlData->user, pUrlData->name); |
| 129 | 128 | }else{ |
| 130 | 129 | zHost = mprintf("%s", pUrlData->name); |
| 131 | 130 | } |
| 132 | - n = blob_size(&zCmd); | |
| 133 | - blob_append(&zCmd, " ", 1); | |
| 134 | - shell_escape(&zCmd, stripLeadingMinus(zHost)); | |
| 135 | - blob_append(&zCmd, " ", 1); | |
| 136 | - shell_escape(&zCmd, mprintf("%s", pUrlData->fossil)); | |
| 137 | - blob_append(&zCmd, " test-http", 10); | |
| 131 | + shell_escape(&aArgv[nToken++], stripLeadingMinus(zHost)); | |
| 132 | + shell_escape(&aArgv[nToken++], mprintf("%s", pUrlData->fossil)); | |
| 133 | + blob_append(&aArgv[nToken++], "test-http", 9); | |
| 138 | 134 | if( pUrlData->path && pUrlData->path[0] ){ |
| 139 | - blob_append(&zCmd, " ", 1); | |
| 140 | - shell_escape(&zCmd, mprintf("%s", stripLeadingMinus(pUrlData->path))); | |
| 135 | + shell_escape(&aArgv[nToken++], | |
| 136 | + mprintf("%s", stripLeadingMinus(pUrlData->path))); | |
| 141 | 137 | } |
| 142 | 138 | if( g.fSshTrace ){ |
| 143 | - fossil_print("%s\n", blob_str(&zCmd)+n); /* Show tail of SSH command */ | |
| 139 | + for(i=0; i<nToken; i++){ | |
| 140 | + fossil_print("%s ", blob_str(&aArgv[i])); | |
| 141 | + } | |
| 142 | + fossil_force_newline(); | |
| 144 | 143 | } |
| 145 | 144 | free(zHost); |
| 146 | - popen2(blob_str(&zCmd), &sshIn, &sshOut, &sshPid); | |
| 145 | + popen2(&aArgv[0], nToken, &sshIn, &sshOut, &sshPid); | |
| 147 | 146 | if( sshPid==0 ){ |
| 148 | - socket_set_errmsg("cannot start ssh tunnel using [%b]", &zCmd); | |
| 147 | + for(i=n; i<nToken; i++){ | |
| 148 | + blob_appendf(&sshBase, " %b", &aArgv[i]); | |
| 149 | + } | |
| 150 | + socket_set_errmsg("cannot start ssh tunnel using [%b]", &sshBase); | |
| 149 | 151 | } |
| 150 | - blob_reset(&zCmd); | |
| 152 | + blobarray_reset(aArgv, nToken); | |
| 151 | 153 | return sshPid==0; |
| 152 | 154 | } |
| 153 | 155 | |
| 154 | 156 | /* |
| 155 | 157 | ** Open a connection to the server. The server is defined by the following |
| 156 | 158 |
| --- src/http_transport.c | |
| +++ src/http_transport.c | |
| @@ -103,53 +103,55 @@ | |
| 103 | */ |
| 104 | int transport_ssh_open(UrlData *pUrlData){ |
| 105 | /* For SSH we need to create and run SSH fossil http |
| 106 | ** to talk to the remote machine. |
| 107 | */ |
| 108 | char *zSsh; /* The base SSH command */ |
| 109 | Blob zCmd; /* The SSH command */ |
| 110 | char *zHost; /* The host name to contact */ |
| 111 | int n; /* Size of prefix string */ |
| 112 | |
| 113 | socket_ssh_resolve_addr(pUrlData); |
| 114 | zSsh = db_get("ssh-command", zDefaultSshCmd); |
| 115 | blob_init(&zCmd, zSsh, -1); |
| 116 | if( pUrlData->port!=pUrlData->dfltPort && pUrlData->port ){ |
| 117 | #ifdef _WIN32 |
| 118 | blob_appendf(&zCmd, " -P %d", pUrlData->port); |
| 119 | #else |
| 120 | blob_appendf(&zCmd, " -p %d", pUrlData->port); |
| 121 | #endif |
| 122 | } |
| 123 | if( g.fSshTrace ){ |
| 124 | fossil_force_newline(); |
| 125 | fossil_print("%s", blob_str(&zCmd)); /* Show the base of the SSH command */ |
| 126 | } |
| 127 | if( pUrlData->user && pUrlData->user[0] ){ |
| 128 | zHost = mprintf("%s@%s", pUrlData->user, pUrlData->name); |
| 129 | }else{ |
| 130 | zHost = mprintf("%s", pUrlData->name); |
| 131 | } |
| 132 | n = blob_size(&zCmd); |
| 133 | blob_append(&zCmd, " ", 1); |
| 134 | shell_escape(&zCmd, stripLeadingMinus(zHost)); |
| 135 | blob_append(&zCmd, " ", 1); |
| 136 | shell_escape(&zCmd, mprintf("%s", pUrlData->fossil)); |
| 137 | blob_append(&zCmd, " test-http", 10); |
| 138 | if( pUrlData->path && pUrlData->path[0] ){ |
| 139 | blob_append(&zCmd, " ", 1); |
| 140 | shell_escape(&zCmd, mprintf("%s", stripLeadingMinus(pUrlData->path))); |
| 141 | } |
| 142 | if( g.fSshTrace ){ |
| 143 | fossil_print("%s\n", blob_str(&zCmd)+n); /* Show tail of SSH command */ |
| 144 | } |
| 145 | free(zHost); |
| 146 | popen2(blob_str(&zCmd), &sshIn, &sshOut, &sshPid); |
| 147 | if( sshPid==0 ){ |
| 148 | socket_set_errmsg("cannot start ssh tunnel using [%b]", &zCmd); |
| 149 | } |
| 150 | blob_reset(&zCmd); |
| 151 | return sshPid==0; |
| 152 | } |
| 153 | |
| 154 | /* |
| 155 | ** Open a connection to the server. The server is defined by the following |
| 156 |
| --- src/http_transport.c | |
| +++ src/http_transport.c | |
| @@ -103,53 +103,55 @@ | |
| 103 | */ |
| 104 | int transport_ssh_open(UrlData *pUrlData){ |
| 105 | /* For SSH we need to create and run SSH fossil http |
| 106 | ** to talk to the remote machine. |
| 107 | */ |
| 108 | Blob sshBase; /* The base SSH command */ |
| 109 | Blob aArgv[50]; /* The SSH arguments array */ |
| 110 | char *zHost; /* The host name to contact */ |
| 111 | int n; /* Size of prefix string */ |
| 112 | int nToken, i; |
| 113 | |
| 114 | socket_ssh_resolve_addr(pUrlData); |
| 115 | blob_init(&sshBase, db_get("ssh-command", zDefaultSshCmd), -1); |
| 116 | blobarray_zero(aArgv, count(aArgv)); |
| 117 | n = nToken = blob_tokenize(&sshBase, aArgv, count(aArgv)); |
| 118 | if( pUrlData->port!=pUrlData->dfltPort && pUrlData->port ){ |
| 119 | #ifdef _WIN32 |
| 120 | blob_append(&aArgv[nToken++], "-P", 2); |
| 121 | #else |
| 122 | blob_append(&aArgv[nToken++], "-p", 2); |
| 123 | #endif |
| 124 | blob_appendf(&aArgv[nToken++], "%d", pUrlData->port); |
| 125 | } |
| 126 | if( pUrlData->user && pUrlData->user[0] ){ |
| 127 | zHost = mprintf("%s@%s", pUrlData->user, pUrlData->name); |
| 128 | }else{ |
| 129 | zHost = mprintf("%s", pUrlData->name); |
| 130 | } |
| 131 | shell_escape(&aArgv[nToken++], stripLeadingMinus(zHost)); |
| 132 | shell_escape(&aArgv[nToken++], mprintf("%s", pUrlData->fossil)); |
| 133 | blob_append(&aArgv[nToken++], "test-http", 9); |
| 134 | if( pUrlData->path && pUrlData->path[0] ){ |
| 135 | shell_escape(&aArgv[nToken++], |
| 136 | mprintf("%s", stripLeadingMinus(pUrlData->path))); |
| 137 | } |
| 138 | if( g.fSshTrace ){ |
| 139 | for(i=0; i<nToken; i++){ |
| 140 | fossil_print("%s ", blob_str(&aArgv[i])); |
| 141 | } |
| 142 | fossil_force_newline(); |
| 143 | } |
| 144 | free(zHost); |
| 145 | popen2(&aArgv[0], nToken, &sshIn, &sshOut, &sshPid); |
| 146 | if( sshPid==0 ){ |
| 147 | for(i=n; i<nToken; i++){ |
| 148 | blob_appendf(&sshBase, " %b", &aArgv[i]); |
| 149 | } |
| 150 | socket_set_errmsg("cannot start ssh tunnel using [%b]", &sshBase); |
| 151 | } |
| 152 | blobarray_reset(aArgv, nToken); |
| 153 | return sshPid==0; |
| 154 | } |
| 155 | |
| 156 | /* |
| 157 | ** Open a connection to the server. The server is defined by the following |
| 158 |
+18
-6
| --- src/popen.c | ||
| +++ src/popen.c | ||
| @@ -111,26 +111,27 @@ | ||
| 111 | 111 | return rc!=0; |
| 112 | 112 | } |
| 113 | 113 | #endif |
| 114 | 114 | |
| 115 | 115 | /* |
| 116 | -** Create a child process running shell command "zCmd". *ppOut is | |
| 116 | +** Create a child process from array of arguments in "aArgv". *ppOut is | |
| 117 | 117 | ** a FILE that becomes the standard input of the child process. |
| 118 | 118 | ** (The caller writes to *ppOut in order to send text to the child.) |
| 119 | 119 | ** *ppIn is stdout from the child process. (The caller |
| 120 | 120 | ** reads from *ppIn in order to receive input from the child.) |
| 121 | 121 | ** Note that *ppIn is an unbuffered file descriptor, not a FILE. |
| 122 | 122 | ** The process ID of the child is written into *pChildPid. |
| 123 | 123 | ** |
| 124 | 124 | ** Return the number of errors. |
| 125 | 125 | */ |
| 126 | -int popen2(const char *zCmd, int *pfdIn, FILE **ppOut, int *pChildPid){ | |
| 126 | +int popen2(Blob *aArgv, int nToken, int *pfdIn, FILE **ppOut, int *pChildPid){ | |
| 127 | 127 | #ifdef _WIN32 |
| 128 | 128 | HANDLE hStdinRd, hStdinWr, hStdoutRd, hStdoutWr, hStderr; |
| 129 | 129 | SECURITY_ATTRIBUTES saAttr; |
| 130 | 130 | DWORD childPid = 0; |
| 131 | - int fd; | |
| 131 | + Blob shellcmd; | |
| 132 | + int fd, i; | |
| 132 | 133 | |
| 133 | 134 | saAttr.nLength = sizeof(saAttr); |
| 134 | 135 | saAttr.bInheritHandle = TRUE; |
| 135 | 136 | saAttr.lpSecurityDescriptor = NULL; |
| 136 | 137 | hStderr = GetStdHandle(STD_ERROR_HANDLE); |
| @@ -142,11 +143,16 @@ | ||
| 142 | 143 | if( !CreatePipe(&hStdinRd, &hStdinWr, &saAttr, 4096) ){ |
| 143 | 144 | win32_fatal_error("cannot create pipe for stdin"); |
| 144 | 145 | } |
| 145 | 146 | SetHandleInformation( hStdinWr, HANDLE_FLAG_INHERIT, FALSE); |
| 146 | 147 | |
| 147 | - win32_create_child_process(fossil_utf8_to_unicode(zCmd), | |
| 148 | + blob_zero(&shellcmd); | |
| 149 | + for(i=0; i<nToken-1; i++){ | |
| 150 | + blob_appendf(&shellcmd, "%b ", aArgv[i]); | |
| 151 | + } | |
| 152 | + blob_trim(&shellcmd); | |
| 153 | + win32_create_child_process(fossil_utf8_to_unicode(blob_str(&shellcmd)), | |
| 148 | 154 | hStdinRd, hStdoutWr, hStderr,&childPid); |
| 149 | 155 | *pChildPid = childPid; |
| 150 | 156 | *pfdIn = _open_osfhandle(PTR_TO_INT(hStdoutRd), 0); |
| 151 | 157 | fd = _open_osfhandle(PTR_TO_INT(hStdinWr), 0); |
| 152 | 158 | *ppOut = _fdopen(fd, "w"); |
| @@ -176,11 +182,12 @@ | ||
| 176 | 182 | *pChildPid = 0; |
| 177 | 183 | return 1; |
| 178 | 184 | } |
| 179 | 185 | signal(SIGPIPE,SIG_IGN); |
| 180 | 186 | if( *pChildPid==0 ){ |
| 181 | - int fd; | |
| 187 | + char **pzArgs = 0; | |
| 188 | + int fd, i; | |
| 182 | 189 | int nErr = 0; |
| 183 | 190 | /* This is the child process */ |
| 184 | 191 | close(0); |
| 185 | 192 | fd = dup(pout[0]); |
| 186 | 193 | if( fd!=0 ) nErr++; |
| @@ -189,11 +196,16 @@ | ||
| 189 | 196 | close(1); |
| 190 | 197 | fd = dup(pin[1]); |
| 191 | 198 | if( fd!=1 ) nErr++; |
| 192 | 199 | close(pin[0]); |
| 193 | 200 | close(pin[1]); |
| 194 | - execl("/bin/sh", "/bin/sh", "-c", zCmd, (char*)0); | |
| 201 | + pzArgs = fossil_malloc( (nToken+1)*sizeof(pzArgs[0]) ); | |
| 202 | + for(i=0; i<nToken; i++){ | |
| 203 | + pzArgs[i] = blob_str(&aArgv[i]); | |
| 204 | + } | |
| 205 | + pzArgs[i] = 0; | |
| 206 | + execvp(pzArgs[0], pzArgs); | |
| 195 | 207 | return 1; |
| 196 | 208 | }else{ |
| 197 | 209 | /* This is the parent process */ |
| 198 | 210 | close(pin[1]); |
| 199 | 211 | *pfdIn = pin[0]; |
| 200 | 212 |
| --- src/popen.c | |
| +++ src/popen.c | |
| @@ -111,26 +111,27 @@ | |
| 111 | return rc!=0; |
| 112 | } |
| 113 | #endif |
| 114 | |
| 115 | /* |
| 116 | ** Create a child process running shell command "zCmd". *ppOut is |
| 117 | ** a FILE that becomes the standard input of the child process. |
| 118 | ** (The caller writes to *ppOut in order to send text to the child.) |
| 119 | ** *ppIn is stdout from the child process. (The caller |
| 120 | ** reads from *ppIn in order to receive input from the child.) |
| 121 | ** Note that *ppIn is an unbuffered file descriptor, not a FILE. |
| 122 | ** The process ID of the child is written into *pChildPid. |
| 123 | ** |
| 124 | ** Return the number of errors. |
| 125 | */ |
| 126 | int popen2(const char *zCmd, int *pfdIn, FILE **ppOut, int *pChildPid){ |
| 127 | #ifdef _WIN32 |
| 128 | HANDLE hStdinRd, hStdinWr, hStdoutRd, hStdoutWr, hStderr; |
| 129 | SECURITY_ATTRIBUTES saAttr; |
| 130 | DWORD childPid = 0; |
| 131 | int fd; |
| 132 | |
| 133 | saAttr.nLength = sizeof(saAttr); |
| 134 | saAttr.bInheritHandle = TRUE; |
| 135 | saAttr.lpSecurityDescriptor = NULL; |
| 136 | hStderr = GetStdHandle(STD_ERROR_HANDLE); |
| @@ -142,11 +143,16 @@ | |
| 142 | if( !CreatePipe(&hStdinRd, &hStdinWr, &saAttr, 4096) ){ |
| 143 | win32_fatal_error("cannot create pipe for stdin"); |
| 144 | } |
| 145 | SetHandleInformation( hStdinWr, HANDLE_FLAG_INHERIT, FALSE); |
| 146 | |
| 147 | win32_create_child_process(fossil_utf8_to_unicode(zCmd), |
| 148 | hStdinRd, hStdoutWr, hStderr,&childPid); |
| 149 | *pChildPid = childPid; |
| 150 | *pfdIn = _open_osfhandle(PTR_TO_INT(hStdoutRd), 0); |
| 151 | fd = _open_osfhandle(PTR_TO_INT(hStdinWr), 0); |
| 152 | *ppOut = _fdopen(fd, "w"); |
| @@ -176,11 +182,12 @@ | |
| 176 | *pChildPid = 0; |
| 177 | return 1; |
| 178 | } |
| 179 | signal(SIGPIPE,SIG_IGN); |
| 180 | if( *pChildPid==0 ){ |
| 181 | int fd; |
| 182 | int nErr = 0; |
| 183 | /* This is the child process */ |
| 184 | close(0); |
| 185 | fd = dup(pout[0]); |
| 186 | if( fd!=0 ) nErr++; |
| @@ -189,11 +196,16 @@ | |
| 189 | close(1); |
| 190 | fd = dup(pin[1]); |
| 191 | if( fd!=1 ) nErr++; |
| 192 | close(pin[0]); |
| 193 | close(pin[1]); |
| 194 | execl("/bin/sh", "/bin/sh", "-c", zCmd, (char*)0); |
| 195 | return 1; |
| 196 | }else{ |
| 197 | /* This is the parent process */ |
| 198 | close(pin[1]); |
| 199 | *pfdIn = pin[0]; |
| 200 |
| --- src/popen.c | |
| +++ src/popen.c | |
| @@ -111,26 +111,27 @@ | |
| 111 | return rc!=0; |
| 112 | } |
| 113 | #endif |
| 114 | |
| 115 | /* |
| 116 | ** Create a child process from array of arguments in "aArgv". *ppOut is |
| 117 | ** a FILE that becomes the standard input of the child process. |
| 118 | ** (The caller writes to *ppOut in order to send text to the child.) |
| 119 | ** *ppIn is stdout from the child process. (The caller |
| 120 | ** reads from *ppIn in order to receive input from the child.) |
| 121 | ** Note that *ppIn is an unbuffered file descriptor, not a FILE. |
| 122 | ** The process ID of the child is written into *pChildPid. |
| 123 | ** |
| 124 | ** Return the number of errors. |
| 125 | */ |
| 126 | int popen2(Blob *aArgv, int nToken, int *pfdIn, FILE **ppOut, int *pChildPid){ |
| 127 | #ifdef _WIN32 |
| 128 | HANDLE hStdinRd, hStdinWr, hStdoutRd, hStdoutWr, hStderr; |
| 129 | SECURITY_ATTRIBUTES saAttr; |
| 130 | DWORD childPid = 0; |
| 131 | Blob shellcmd; |
| 132 | int fd, i; |
| 133 | |
| 134 | saAttr.nLength = sizeof(saAttr); |
| 135 | saAttr.bInheritHandle = TRUE; |
| 136 | saAttr.lpSecurityDescriptor = NULL; |
| 137 | hStderr = GetStdHandle(STD_ERROR_HANDLE); |
| @@ -142,11 +143,16 @@ | |
| 143 | if( !CreatePipe(&hStdinRd, &hStdinWr, &saAttr, 4096) ){ |
| 144 | win32_fatal_error("cannot create pipe for stdin"); |
| 145 | } |
| 146 | SetHandleInformation( hStdinWr, HANDLE_FLAG_INHERIT, FALSE); |
| 147 | |
| 148 | blob_zero(&shellcmd); |
| 149 | for(i=0; i<nToken-1; i++){ |
| 150 | blob_appendf(&shellcmd, "%b ", aArgv[i]); |
| 151 | } |
| 152 | blob_trim(&shellcmd); |
| 153 | win32_create_child_process(fossil_utf8_to_unicode(blob_str(&shellcmd)), |
| 154 | hStdinRd, hStdoutWr, hStderr,&childPid); |
| 155 | *pChildPid = childPid; |
| 156 | *pfdIn = _open_osfhandle(PTR_TO_INT(hStdoutRd), 0); |
| 157 | fd = _open_osfhandle(PTR_TO_INT(hStdinWr), 0); |
| 158 | *ppOut = _fdopen(fd, "w"); |
| @@ -176,11 +182,12 @@ | |
| 182 | *pChildPid = 0; |
| 183 | return 1; |
| 184 | } |
| 185 | signal(SIGPIPE,SIG_IGN); |
| 186 | if( *pChildPid==0 ){ |
| 187 | char **pzArgs = 0; |
| 188 | int fd, i; |
| 189 | int nErr = 0; |
| 190 | /* This is the child process */ |
| 191 | close(0); |
| 192 | fd = dup(pout[0]); |
| 193 | if( fd!=0 ) nErr++; |
| @@ -189,11 +196,16 @@ | |
| 196 | close(1); |
| 197 | fd = dup(pin[1]); |
| 198 | if( fd!=1 ) nErr++; |
| 199 | close(pin[0]); |
| 200 | close(pin[1]); |
| 201 | pzArgs = fossil_malloc( (nToken+1)*sizeof(pzArgs[0]) ); |
| 202 | for(i=0; i<nToken; i++){ |
| 203 | pzArgs[i] = blob_str(&aArgv[i]); |
| 204 | } |
| 205 | pzArgs[i] = 0; |
| 206 | execvp(pzArgs[0], pzArgs); |
| 207 | return 1; |
| 208 | }else{ |
| 209 | /* This is the parent process */ |
| 210 | close(pin[1]); |
| 211 | *pfdIn = pin[0]; |
| 212 |