mirror of
https://github.com/sqlite/sqlite.git
synced 2026-10-23 21:49:02 +03:00
Improved escaping of filenames with unusual characters sent over the SSH
link by the sqlite3_rsync utility program. ANT-2026-31X9HVRR. FossilOrigin-Name: eaec2f7f3744d4cdf0cdafce22e1909208f93734bf55263a042938919bd81310
This commit is contained in:
14
manifest
14
manifest
@@ -1,5 +1,5 @@
|
||||
C Mark\svec1\straining\sfunction\svec1_train()\sas\sSQLITE_DIRECTONLY,\son\sthe\sgrounds\sthat\sit\ssometimes\sprepares\sand\sexecutes\sSQL\sstatements\sbased\son\suser-supplied\sarguments.
|
||||
D 2026-10-07T21:12:50.063
|
||||
C Improved\sescaping\sof\sfilenames\swith\sunusual\scharacters\ssent\sover\sthe\sSSH\nlink\sby\sthe\ssqlite3_rsync\sutility\sprogram.\s\sANT-2026-31X9HVRR.
|
||||
D 2026-10-07T23:07:55.753
|
||||
F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
|
||||
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
|
||||
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
|
||||
@@ -2261,7 +2261,7 @@ F tool/spellsift.tcl 52b4b04dc4333c7ab024f09d9d66ed6b6f7c6eb00b38497a09f338fa55d
|
||||
F tool/split-sqlite3c.tcl 4969fd642dad0ea483e4e104163021d92baf98f6a8eac981fe48525f9b873430
|
||||
F tool/sqldiff.c c07b741de3fc629e30d7fcab9db04de94e94d7743ab38cadd778b2b1363f8d92
|
||||
F tool/sqlite3_analyzer.c.in 14f02cb5ec3c264cd6107d1f1dad77092b1cf440fc196c30b69ae87b56a1a43b
|
||||
F tool/sqlite3_rsync.c 160473120adc20ef1763d0b274cf618e83cb1aaf59dd479349a3daa5b7fb1e9d
|
||||
F tool/sqlite3_rsync.c a342f9762653f968e12993bb2dc9083c52ad755c23f6005c575d8a376f970422
|
||||
F tool/sqltclsh.c.in c103c6fc7d42bce611f9d4596774d60b7ef3d0b291a1f58c9e6184e458b89296
|
||||
F tool/sqltclsh.tcl 862f4cf1418df5e1315b5db3b5ebe88969e2a784525af5fbf9596592f14ed848
|
||||
F tool/src-verify.c 6c655d9a8d6b30f3648fc78a79bf3838ed68f8543869d380c43ea9f17b3b8501
|
||||
@@ -2280,8 +2280,8 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee
|
||||
F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2
|
||||
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
|
||||
F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c
|
||||
P 2662a68f0d5ce1c8ffd6e8ec3e79b27f7cb5e6142a007a6641e1ac0b5a22037a
|
||||
R 4db96a87f82315b023b2499afdf3d391
|
||||
U dan
|
||||
Z ef9c919f90a321e41f9232bded41e49a
|
||||
P 38c5972e914ba8c54b76f6de89501fce576c4a05a7f88f90c39b268203f54582
|
||||
R 87d911f0de84b405bcc492d75ba4f864
|
||||
U drh
|
||||
Z 6d1b76a52e6bc82c7648e39f5983b795
|
||||
# Remove this line to create a well-formed Fossil manifest.
|
||||
|
||||
@@ -1 +1 @@
|
||||
38c5972e914ba8c54b76f6de89501fce576c4a05a7f88f90c39b268203f54582
|
||||
eaec2f7f3744d4cdf0cdafce22e1909208f93734bf55263a042938919bd81310
|
||||
|
||||
@@ -382,22 +382,7 @@ static int pclose2(FILE *pIn, FILE *pOut, int childPid){
|
||||
** 5 First byte of a 4-byte UTF-8
|
||||
*/
|
||||
static const char aSafeChar[256] = {
|
||||
#ifdef _WIN32
|
||||
/* Windows
|
||||
** Prohibit: all control characters, including tab, \r and \n.
|
||||
** Escape: (space) " # $ % & ' ( ) * ; < > ? [ ] ^ ` { | }
|
||||
*/
|
||||
/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xa xb xc xd xe xf */
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0x */
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 1x */
|
||||
1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 2x */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, /* 3x */
|
||||
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 4x */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, /* 5x */
|
||||
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 6x */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, /* 7x */
|
||||
#else
|
||||
/* Unix
|
||||
/*
|
||||
** Prohibit: all control characters, including tab, \r and \n
|
||||
** Escape: (space) ! " # $ % & ' ( ) * ; < > ? [ \ ] ^ ` { | }
|
||||
*/
|
||||
@@ -410,7 +395,6 @@ static const char aSafeChar[256] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, /* 5x */
|
||||
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 6x */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, /* 7x */
|
||||
#endif
|
||||
/* all bytes 0x80 through 0xbf are unescaped, being secondary
|
||||
** bytes to UTF8 characters. Bytes 0xc0 through 0xff are the
|
||||
** first byte of a UTF8 character and do get escaped */
|
||||
@@ -424,52 +408,56 @@ static const char aSafeChar[256] = {
|
||||
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 /* fx */
|
||||
};
|
||||
|
||||
/*
|
||||
** Return true if the input contains special characters that need
|
||||
** to be escaped.
|
||||
*/
|
||||
int need_escape(const char *zIn){
|
||||
size_t i;
|
||||
unsigned char c;
|
||||
|
||||
/* Look for characters that need to be escaped */
|
||||
for(i=0; (c = (unsigned char)zIn[i])!=0; i++){
|
||||
if( aSafeChar[c] ) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Increment this global variable whenever the isFilename==2 hex encoding
|
||||
** is used.
|
||||
*/
|
||||
static int nHexEncode = 0;
|
||||
|
||||
/*
|
||||
** pStr is a shell command under construction. This routine safely
|
||||
** appends filename argument zIn. It returns 0 on success or non-zero
|
||||
** on any error.
|
||||
** appends filename argument zIn.
|
||||
**
|
||||
** The argument is escaped if it contains white space or other characters
|
||||
** that need to be escaped for the shell. If zIn contains characters
|
||||
** that cannot be safely escaped, then throw a fatal error.
|
||||
**
|
||||
** If the isFilename argument is true, then the argument is expected
|
||||
** to be a filename. As shell commands commonly have command-line
|
||||
** If the isFilename argument is 1 or 2, then the argument is expected
|
||||
** to be a filename. isFilename==1 means to use shell-style escaping
|
||||
** as the argument is going directly to the shell. isFilename==2 means
|
||||
** to escape as hexadecimal (using A-P as the hex digits) instead of
|
||||
** shell escaping. isFilename==2 argument are interpreted by another
|
||||
** instance of sqlite3_rsync which knows how to decode them.
|
||||
**
|
||||
** For isFilename==1, because shell commands commonly have command-line
|
||||
** options that begin with "-" and since we do not want an attacker
|
||||
** to be able to invoke these switches using filenames that begin
|
||||
** with "-", if zIn begins with "-", prepend an additional "./"
|
||||
** (or ".\\" on Windows).
|
||||
** with "-", if zIn begins with "-", prepend an additional "./".
|
||||
**
|
||||
** For isFilename==2, if the filename could be mistaken for a hexadecimal
|
||||
** string (if it is an even number of characters and all characters are
|
||||
** between A and P) then "./" is prepended so that the receiver
|
||||
** will know that it is not an encoded filename.
|
||||
*/
|
||||
int append_escaped_arg(sqlite3_str *pStr, const char *zIn, int isFilename){
|
||||
int i;
|
||||
unsigned char c;
|
||||
int needEscape = 0;
|
||||
void append_escaped_arg(sqlite3_str *pStr, const char *zIn, int isFilename){
|
||||
int n = sqlite3_str_length(pStr);
|
||||
char *z = sqlite3_str_value(pStr);
|
||||
|
||||
/* Look for illegal byte-sequences and byte-sequences that require
|
||||
** escaping. No control-characters are allowed. All spaces and
|
||||
** non-ASCII unicode characters and some punctuation characters require
|
||||
** escaping. */
|
||||
for(i=0; (c = (unsigned char)zIn[i])!=0; i++){
|
||||
if( aSafeChar[c] ){
|
||||
unsigned char x = aSafeChar[c];
|
||||
needEscape = 1;
|
||||
if( x==2 ){
|
||||
/* Bad ASCII character */
|
||||
return 1;
|
||||
}else if( x>2 ){
|
||||
if( (zIn[i+1]&0xc0)!=0x80
|
||||
|| (x>=4 && (zIn[i+2]&0xc0)!=0x80)
|
||||
|| (x==5 && (zIn[i+3]&0xc0)!=0x80)
|
||||
){
|
||||
/* Bad UTF8 character */
|
||||
return 1;
|
||||
}
|
||||
i += x-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
size_t nIn = strlen(zIn);
|
||||
|
||||
/* Separate from the previous argument by a space */
|
||||
if( n>0 && !isspace(z[n-1]) ){
|
||||
@@ -477,22 +465,31 @@ int append_escaped_arg(sqlite3_str *pStr, const char *zIn, int isFilename){
|
||||
}
|
||||
|
||||
/* Check for characters that need quoting */
|
||||
if( !needEscape ){
|
||||
if( isFilename && zIn[0]=='-' ){
|
||||
if( !need_escape(zIn) ){
|
||||
if( (isFilename>=1 && zIn[0]=='-')
|
||||
|| (isFilename==2 && (nIn&1)==0 && strspn(zIn,"ABCDEFGHIJKLMNOP")==nIn)
|
||||
){
|
||||
sqlite3_str_appendchar(pStr, 1, '.');
|
||||
#if defined(_WIN32)
|
||||
sqlite3_str_appendchar(pStr, 1, '\\');
|
||||
#else
|
||||
sqlite3_str_appendchar(pStr, 1, '/');
|
||||
#endif
|
||||
}
|
||||
sqlite3_str_appendall(pStr, zIn);
|
||||
}else if( isFilename==2 ){
|
||||
/* Encode as hexadecimal using A-P as the hex digits. */
|
||||
size_t j;
|
||||
for(j=0; j<nIn; j++){
|
||||
sqlite3_str_appendchar(pStr, 1, (((zIn[j])>>4)&0xf)+'A');
|
||||
sqlite3_str_appendchar(pStr, 1, ((zIn[j])&0xf)+'A');
|
||||
}
|
||||
nHexEncode++;
|
||||
}else{
|
||||
/* Use shell escapes */
|
||||
#if defined(_WIN32)
|
||||
/* Quoting strategy for windows:
|
||||
** Put the entire name inside of "...". Any " characters within
|
||||
** the name get doubled.
|
||||
*/
|
||||
size_t i;
|
||||
unsigned char c;
|
||||
sqlite3_str_appendchar(pStr, 1, '"');
|
||||
if( isFilename && zIn[0]=='-' ){
|
||||
sqlite3_str_appendchar(pStr, 1, '.');
|
||||
@@ -514,6 +511,8 @@ int append_escaped_arg(sqlite3_str *pStr, const char *zIn, int isFilename){
|
||||
** name, then put \ before each special character.
|
||||
*/
|
||||
if( strchr(zIn,'\'') ){
|
||||
unsigned char c;
|
||||
size_t i;
|
||||
if( isFilename && zIn[0]=='-' ){
|
||||
sqlite3_str_appendchar(pStr, 1, '.');
|
||||
sqlite3_str_appendchar(pStr, 1, '/');
|
||||
@@ -535,7 +534,6 @@ int append_escaped_arg(sqlite3_str *pStr, const char *zIn, int isFilename){
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Add an approprate PATH= argument to the SSH command under construction
|
||||
@@ -585,6 +583,36 @@ void add_path_argument(sqlite3_str *pStr){
|
||||
":/opt/local/bin:$PATH", 0);
|
||||
}
|
||||
|
||||
/*
|
||||
** The input is a filename, possibly encoded as hexadecimal using
|
||||
** characters 'A' through 'P' as the hexadecimal digits (instead of
|
||||
** the usual '0' through 'F').
|
||||
**
|
||||
** Return a copy of that filename, decoded if necessary.
|
||||
*/
|
||||
static char *decode_filename(const char *zIn){
|
||||
size_t n;
|
||||
if( zIn==0 ) return 0;
|
||||
n = strlen(zIn);
|
||||
if( (n&1)==1 || strspn(zIn,"ABCDEFGHIJKLMNOP")!=n ){
|
||||
/* Name is literal. No decoding required. */
|
||||
return sqlite3_mprintf("%s", zIn);
|
||||
}else{
|
||||
/* Name is hex encoded. Need to decode it. */
|
||||
char *zOut = sqlite3_malloc64(n/2 + 1);
|
||||
size_t i;
|
||||
if( zOut==0 ){
|
||||
fprintf(stderr, "out of memory\n");
|
||||
exit(1);
|
||||
}
|
||||
for(i=0; i<n/2; i++){
|
||||
zOut[i] = (zIn[i*2]-'A')*16 + zIn[i*2+1]-'A';
|
||||
}
|
||||
zOut[i] = 0;
|
||||
return zOut;
|
||||
}
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
** End of the append_escaped_arg() routine, adapted from the Fossil **
|
||||
*****************************************************************************/
|
||||
@@ -2087,6 +2115,7 @@ int main(int argc, char const * const *argv){
|
||||
sqlite3_int64 tmElapse;
|
||||
const char *zRemoteErrFile = 0;
|
||||
const char *zRemoteDebugFile = 0;
|
||||
int nEncoded = 0;
|
||||
|
||||
#define cli_opt_val cmdline_option_value(argc, argv, ++i)
|
||||
memset(&ctx, 0, sizeof(ctx));
|
||||
@@ -2131,12 +2160,26 @@ int main(int argc, char const * const *argv){
|
||||
}
|
||||
if( strcmp(z, "-exe")==0 ){
|
||||
zExe = cli_opt_val;
|
||||
if( need_escape(zExe) ){
|
||||
fprintf(stderr, "executable name uses special characters: \"%s\"\n",
|
||||
zExe);
|
||||
return 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if( strcmp(z, "-wal-only")==0 ){
|
||||
ctx.bWalOnly = 1;
|
||||
continue;
|
||||
}
|
||||
if( strcmp(z, "-encoded-filenames")==0 ){
|
||||
/* Internal use only */
|
||||
nEncoded = atoi(cli_opt_val);
|
||||
if( nEncoded<1 ){
|
||||
fprintf(stderr, "invalid argument to %s\n", argv[i-1]);
|
||||
return 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if( strcmp(z, "-version")==0 ){
|
||||
printf("%s\n", sqlite3_sourceid());
|
||||
return 0;
|
||||
@@ -2201,6 +2244,15 @@ int main(int argc, char const * const *argv){
|
||||
append_escaped_arg(pStr, argv[k], i!=k);
|
||||
}
|
||||
printf("%s\n", sqlite3_str_value(pStr));
|
||||
|
||||
/* Do it a second time using the hex encoding */
|
||||
pStr = sqlite3_str_new(0);
|
||||
nHexEncode = 0;
|
||||
for(k=0; k<argc; k++){
|
||||
append_escaped_arg(pStr, argv[k], (i!=k)*2);
|
||||
}
|
||||
printf("%s\n", sqlite3_str_value(pStr));
|
||||
printf("nHexEncode: %d\n", nHexEncode);
|
||||
return 0;
|
||||
}
|
||||
if( z[0]=='-' ){
|
||||
@@ -2225,9 +2277,23 @@ int main(int argc, char const * const *argv){
|
||||
fprintf(stderr, "missing REPLICA database filename\n");
|
||||
return 1;
|
||||
}
|
||||
if( isOrigin && isReplica ){
|
||||
fprintf(stderr, "bad option combination\n");
|
||||
return 1;
|
||||
if( isOrigin || isReplica ){
|
||||
if( isOrigin && isReplica ){
|
||||
fprintf(stderr, "bad option combination\n");
|
||||
return 1;
|
||||
}
|
||||
if( nEncoded ){
|
||||
ctx.zOrigin = decode_filename(ctx.zOrigin);
|
||||
ctx.zReplica = decode_filename(ctx.zReplica);
|
||||
ctx.zErrFile = decode_filename(ctx.zErrFile);
|
||||
ctx.zDebugFile = decode_filename(ctx.zDebugFile);
|
||||
}
|
||||
}else{
|
||||
/* Must be the local side */
|
||||
if( nHexEncode ){
|
||||
fprintf(stderr,"incorrect use of \"--encoded-filenames\"\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if( isOrigin ){
|
||||
ctx.pIn = stdin;
|
||||
@@ -2251,10 +2317,6 @@ int main(int argc, char const * const *argv){
|
||||
replicaSide(&ctx);
|
||||
return 0;
|
||||
}
|
||||
if( ctx.zReplica==0 ){
|
||||
fprintf(stderr, "missing REPLICA database filename\n");
|
||||
return 1;
|
||||
}
|
||||
tmStart = currentTime();
|
||||
zDiv = hostSeparator(ctx.zOrigin);
|
||||
if( zDiv ){
|
||||
@@ -2282,17 +2344,20 @@ int main(int argc, char const * const *argv){
|
||||
}
|
||||
if( zRemoteErrFile ){
|
||||
append_escaped_arg(pStr, "--errorfile", 0);
|
||||
append_escaped_arg(pStr, zRemoteErrFile, 1);
|
||||
append_escaped_arg(pStr, zRemoteErrFile, 2);
|
||||
}
|
||||
if( zRemoteDebugFile ){
|
||||
append_escaped_arg(pStr, "--debugfile", 0);
|
||||
append_escaped_arg(pStr, zRemoteDebugFile, 1);
|
||||
append_escaped_arg(pStr, zRemoteDebugFile, 2);
|
||||
}
|
||||
if( ctx.bWalOnly ){
|
||||
append_escaped_arg(pStr, "--wal-only", 0);
|
||||
}
|
||||
append_escaped_arg(pStr, zDiv, 1);
|
||||
append_escaped_arg(pStr, file_tail(ctx.zReplica), 1);
|
||||
append_escaped_arg(pStr, zDiv, 2);
|
||||
append_escaped_arg(pStr, file_tail(ctx.zReplica), 2);
|
||||
if( nHexEncode>0 ){
|
||||
sqlite3_str_appendf(pStr, " --encoded-filenames %d", nHexEncode);
|
||||
}
|
||||
if( ctx.eVerbose<2 && iRetry==0 ){
|
||||
append_escaped_arg(pStr, "2>/dev/null", 0);
|
||||
}
|
||||
@@ -2331,17 +2396,20 @@ int main(int argc, char const * const *argv){
|
||||
}
|
||||
if( zRemoteErrFile ){
|
||||
append_escaped_arg(pStr, "--errorfile", 0);
|
||||
append_escaped_arg(pStr, zRemoteErrFile, 1);
|
||||
append_escaped_arg(pStr, zRemoteErrFile, 2);
|
||||
}
|
||||
if( zRemoteDebugFile ){
|
||||
append_escaped_arg(pStr, "--debugfile", 0);
|
||||
append_escaped_arg(pStr, zRemoteDebugFile, 1);
|
||||
append_escaped_arg(pStr, zRemoteDebugFile, 2);
|
||||
}
|
||||
if( ctx.bWalOnly ){
|
||||
append_escaped_arg(pStr, "--wal-only", 0);
|
||||
}
|
||||
append_escaped_arg(pStr, file_tail(ctx.zOrigin), 1);
|
||||
append_escaped_arg(pStr, zDiv, 1);
|
||||
append_escaped_arg(pStr, file_tail(ctx.zOrigin), 2);
|
||||
append_escaped_arg(pStr, zDiv, 2);
|
||||
if( nHexEncode>0 ){
|
||||
sqlite3_str_appendf(pStr, " --encoded-filenames %d", nHexEncode);
|
||||
}
|
||||
if( ctx.eVerbose<2 && iRetry==0 ){
|
||||
append_escaped_arg(pStr, "2>/dev/null", 0);
|
||||
}
|
||||
@@ -2370,14 +2438,17 @@ int main(int argc, char const * const *argv){
|
||||
}
|
||||
if( zRemoteErrFile ){
|
||||
append_escaped_arg(pStr, "--errorfile", 0);
|
||||
append_escaped_arg(pStr, zRemoteErrFile, 1);
|
||||
append_escaped_arg(pStr, zRemoteErrFile, 2);
|
||||
}
|
||||
if( zRemoteDebugFile ){
|
||||
append_escaped_arg(pStr, "--debugfile", 0);
|
||||
append_escaped_arg(pStr, zRemoteDebugFile, 1);
|
||||
append_escaped_arg(pStr, zRemoteDebugFile, 2);
|
||||
}
|
||||
append_escaped_arg(pStr, ctx.zOrigin, 2);
|
||||
append_escaped_arg(pStr, ctx.zReplica, 2);
|
||||
if( nHexEncode>0 ){
|
||||
sqlite3_str_appendf(pStr, " --encoded-filenames %d", nHexEncode);
|
||||
}
|
||||
append_escaped_arg(pStr, ctx.zOrigin, 1);
|
||||
append_escaped_arg(pStr, ctx.zReplica, 1);
|
||||
zCmd = sqlite3_str_finish(pStr);
|
||||
if( ctx.eVerbose>=2 ) printf("%s\n", zCmd);
|
||||
if( popen2(zCmd, &ctx.pIn, &ctx.pOut, &childPid, 0) ){
|
||||
|
||||
Reference in New Issue
Block a user