1
0
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:
drh
2026-10-07 23:07:55 +00:00
parent aa49b4609a
commit b0db2db988
3 changed files with 156 additions and 85 deletions

View File

@@ -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.

View File

@@ -1 +1 @@
38c5972e914ba8c54b76f6de89501fce576c4a05a7f88f90c39b268203f54582
eaec2f7f3744d4cdf0cdafce22e1909208f93734bf55263a042938919bd81310

View File

@@ -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) ){