From a87eb97e0840f4a66967bdc018a935144562200d Mon Sep 17 00:00:00 2001 From: drh <> Date: Wed, 14 Oct 2026 20:06:21 +0000 Subject: [PATCH] Initial implementation of the experimental sandbox(N1,E,N2) function. The function resolves (by transforming the AST) to the expression E if N1 and N2 are the same constant expression. Requires the -DSQLITE_ENABLE_SANDBOX_FUNCTION compile-time option. FossilOrigin-Name: 035fd52d54a6f8f9c8276a48f466d660832d46d05a02b3ee4a41959922a4bc22 --- manifest | 20 +++++++++------- manifest.tags | 4 ++-- manifest.uuid | 2 +- src/expr.c | 59 +++++++++++++++++++++++++++++++++++++++++------ src/test_config.c | 6 +++++ test/sandbox1.sql | 48 ++++++++++++++++++++++++++++++++++++++ tool/mkctimec.tcl | 1 + 7 files changed, 122 insertions(+), 18 deletions(-) create mode 100644 test/sandbox1.sql diff --git a/manifest b/manifest index 88fbffec84..6ec376b594 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\sindexes\son\stypeless\sgenerated\scolumns\sthat\spull\sfrom\sREAL\scolumns\sso\nthat\sthey\scorrectly\sreturn\sREAL\svalues.\n[bugs:/info/2026-10-14T17:34:30Z|Bug\s2026-10-14T17:34:30Z]. -D 2026-10-14T18:39:44.984 +C Initial\simplementation\sof\sthe\sexperimental\ssandbox(N1,E,N2)\sfunction.\nThe\sfunction\sresolves\s(by\stransforming\sthe\sAST)\sto\sthe\sexpression\sE\sif\nN1\sand\sN2\sare\sthe\ssame\sconstant\sexpression.\s\sRequires\sthe\n-DSQLITE_ENABLE_SANDBOX_FUNCTION\scompile-time\soption. +D 2026-10-14T20:06:21.237 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -755,7 +755,7 @@ F src/date.c 080641f59097d14fc7341f8bc0458d273b3a62d8381916db8ccbc89c2aae07b3 F src/dbpage.c ec8590c5e7a564cef28ca3c6156521316a4d26cb572a34841b0bcb26b67b3f97 F src/dbstat.c 34d4e825fbf763640ce3dbc56910ab080495ef4a6efb48b2b1852a43d43763fc F src/delete.c f3f07c1fb6ac87fac2425d48fd40ecb2f24a4495a9966f5a939668d3576916c8 -F src/expr.c e4097826bbb853e233c1f4f27fd93f2ebc48104eb88d7ac9ba0541ddb631d5c7 +F src/expr.c 5ba49ab1ace9fff5a551b71c148945e22cffc4de008eef678f3acab688a31028 F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007 F src/fkey.c 26788ae6c785d70f9829c4176e9764447014fc73e6ba31e9f1881720a64ae7f1 F src/func.c 9372b65d0e5e6fc9fff7c4fc20558c5e68babcc7f54f033bba67da39f47acc37 @@ -828,7 +828,7 @@ F src/test_backup.c a2bfd90d2ff2511b8635507bdb30fa9b605ade19c16b533066cae3077f5b F src/test_bestindex.c d75fad21369d80910238032bcf8d9ca1f2bffda13c1ceec63bfbb7f704448b15 F src/test_blob.c 3a946319c1b062a58d249c67be099c3bc1ec52f2e6ba23b1867699f0b8189ac2 F src/test_btree.c 28283787d32b8fa953eb77412ad0de2c9895260e4e5bd5a94b3c7411664f90d5 -F src/test_config.c 45b83ce7981fd01c58e85b67955bc6a344da5abfb4db1c3f2ba6dcc7adc85d83 +F src/test_config.c ff851c26abc3e994b1a21b4b410e165538c2b5ee990cf4a171ef32d07e626c1f F src/test_delete.c 4abfe8cb9da1f7faf725b18ae93391485e8d8c7df0bad9dcb68f6066add5b8c3 F src/test_demovfs.c 3efa2adf4f21e10d95521721687d5ca047aea91fa62dd8cc22ac9e5a9c942383 F src/test_devsym.c 649434ed34d0b03fbd5a6b42df80f0f9a7e53f94dd1710aad5dd8831e91c4e86 @@ -1654,6 +1654,7 @@ F test/rowvaluevtab.test cd9747bb3f308086944c07968f547ad6b05022e698d80b9ffbdfe09 F test/rtree.test 0c8d9dd458d6824e59683c19ab2ffa9ef946f798 F test/run-wordcount.sh 891e89c4c2d16e629cd45951d4ed899ad12afc09 F test/safemode.sql 8373efb03c49a9ceae81fa6d9216c69d7e8573351b273b3f82ae2c5ad9305f99 +F test/sandbox1.sql 35ec3792bde234d03b446a0d5551dba210d4a7bdfb3ee64cd4b7d18cdbd2a613 F test/savepoint.test 6e9804a17767f08432c7a5e738b9a8f4b891d243110b63d3a41d270d3d1378ec F test/savepoint2.test 9b8543940572a2f01a18298c3135ad0c9f4f67d7 F test/savepoint4.test c8f8159ade6d2acd9128be61e1230f1c1edc6cc0 @@ -2230,7 +2231,7 @@ F tool/mkamalzip.tcl 8aa5ebe7973c8b8774062d34e15fea9815c4cc2ceea3a9b184695f00591 F tool/mkautoconfamal.sh 06fbe090b81c24e592c1f22b404334f805ba74d482a9260f2ac81e6f3d3386d8 F tool/mkccode.tcl c42a8f8cf78f92e83795d5447460dbce7aaf78a3bbf9082f1507dc71a3665f3c x F tool/mkcombo.tcl 2a5189b219c4a495e1ff7fc980bd568d3cfb82ae9d50c84e77f7a161e96fc132 -F tool/mkctimec.tcl 85ce722e10f81368bc42fb194fe6af450f7f0ef9199b706cbc02b5b484ce3d4a x +F tool/mkctimec.tcl 4116e0190e8fc1016a922d6dfd33dcf936d9ee57e0ed28fd788dc238c909729b x F tool/mkfptab.c 24ea40113f96584caca3f6dd06b4ad5adffa0f39023910cb83fa7ef2ffa9b0ba F tool/mkkeywordhash.c 82d5af1d0e677900739fba59155cddac172d8c712c2d91ab73d6e6bcb30060f0 F tool/mkmsvcmin.tcl d76c45efda1cce2d4005bcea7b8a22bb752e3256009f331120fb4fecb14ebb7a @@ -2289,8 +2290,11 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P eaf646e376a5a3e10a874ed1c07ca1382f0ccb7cdf1cc963697638a54b98c779 -R cd5e05e09f37035495964650d20d3282 +P 7408f90ecc2dd26aceb52f92de78b8e1521cb5a294d3231fd32a903afe1db41d +R 2f6a7a6115fad7c13aff2db60c940aac +T *branch * sandbox-func +T *sym-sandbox-func * +T -sym-trunk * U drh -Z 60c70d9c1707258a8e85a246e04bf473 +Z f504d7ec1dca2d8a14e6b845203e3501 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.tags b/manifest.tags index bec971799f..4b21a5b8eb 100644 --- a/manifest.tags +++ b/manifest.tags @@ -1,2 +1,2 @@ -branch trunk -tag trunk +branch sandbox-func +tag sandbox-func diff --git a/manifest.uuid b/manifest.uuid index 77a70bb840..69c6435f32 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -7408f90ecc2dd26aceb52f92de78b8e1521cb5a294d3231fd32a903afe1db41d +035fd52d54a6f8f9c8276a48f466d660832d46d05a02b3ee4a41959922a4bc22 diff --git a/src/expr.c b/src/expr.c index cc8d37c1f6..80aa520d45 100644 --- a/src/expr.c +++ b/src/expr.c @@ -1181,6 +1181,42 @@ Expr *sqlite3ExprAnd(Parse *pParse, Expr *pLeft, Expr *pRight){ } } +#if SQLITE_ENABLE_SANDBOX_FUNCTION+0>0 +/* +** Magical SQL function: sandbox(N1, E, N2) +** +** This function is magical in that it is evaluated at compile-time: No +** bytecode is generated. Instead sandbox() transforms the AST. +** +** The AST for the sandbox() itself is replaced with the middle expression E +** if and only if all of the following are true: +** +** * N1 and N2 must compare equal using sqlite3ExprCompare(). +** +** * N1 (and hence N2 also, due to the prior constrant) must both be +** a constant expression. It cannot use variables or table references. +** +** * E does not contain any subqueries - either scalar subqueries or +** "EXISTS" expressions or IN operators where the RHS is a subquery. +*/ +static SQLITE_NOINLINE Expr *exprSandbox(Parse *pParse, Expr *pOrig){ + struct ExprList_item *a = pOrig->x.pList->a; + Expr *pMid = a[1].pExpr; + if( sqlite3ExprCompare(pParse, a[0].pExpr, a[2].pExpr, 0)!=0 + || sqlite3ExprIsConstant(pParse, a[0].pExpr)==0 + || sqlite3ExprIsConstantOrFunction(a[0].pExpr, 0)==0 + || sqlite3ExprContainsSubquery(pMid) + ){ + sqlite3ErrorMsg(pParse, "sandbox violation"); + return pOrig; + } + a[1] = a[2]; + pOrig->x.pList->nExpr = 2; + sqlite3ExprDelete(pParse->db, pOrig); + return pMid; +} +#endif /* SQLITE_ENABLE_SANDBOX_FUNCTION */ + /* ** Construct a new expression node for a function with multiple ** arguments. @@ -1194,6 +1230,12 @@ Expr *sqlite3ExprFunction( Expr *pNew; sqlite3 *db = pParse->db; assert( pToken ); + if( pList + && pList->nExpr > pParse->db->aLimit[SQLITE_LIMIT_FUNCTION_ARG] + && !pParse->nested + ){ + sqlite3ErrorMsg(pParse, "too many arguments on function %T", pToken); + } pNew = sqlite3ExprAlloc(db, TK_FUNCTION, pToken, 1); if( pNew==0 ){ sqlite3ExprListDelete(db, pList); /* Avoid memory leak when malloc fails */ @@ -1201,17 +1243,20 @@ Expr *sqlite3ExprFunction( } assert( !ExprHasProperty(pNew, EP_InnerON|EP_OuterON) ); pNew->w.iOfst = (int)(pToken->z - pParse->zTail); - if( pList - && pList->nExpr > pParse->db->aLimit[SQLITE_LIMIT_FUNCTION_ARG] - && !pParse->nested - ){ - sqlite3ErrorMsg(pParse, "too many arguments on function %T", pToken); - } pNew->x.pList = pList; ExprSetProperty(pNew, EP_HasFunc); assert( ExprUseXList(pNew) ); sqlite3ExprSetHeightAndFlags(pParse, pNew); if( eDistinct==SF_Distinct ) ExprSetProperty(pNew, EP_Distinct); +#if SQLITE_ENABLE_SANDBOX_FUNCTION+0>0 + if( pToken->n==7 + && pList!=0 + && pList->nExpr==3 + && sqlite3_strnicmp(pToken->z,"SANDBOX",7)==0 + ){ + pNew = exprSandbox(pParse, pNew); + } +#endif /* SQLITE_ENABLE_SANDBOX_FUNCTION */ return pNew; } @@ -2901,7 +2946,7 @@ int sqlite3ExprIsConstantOrGroupBy(Parse *pParse, Expr *p, ExprList *pGroupBy){ ** in a CREATE TABLE statement. Return non-zero if the expression is ** acceptable for use as a DEFAULT. That is to say, return non-zero if ** the expression is constant or a function call with constant arguments. -** Return and 0 if there are any variables. +** Return 0 if there are any variables. ** ** isInit is true when parsing from sqlite_schema. isInit is false when ** processing a new CREATE TABLE statement. When isInit is true, parameters diff --git a/src/test_config.c b/src/test_config.c index 7338622ad8..c7ae2b2ee1 100644 --- a/src/test_config.c +++ b/src/test_config.c @@ -209,6 +209,12 @@ static void set_options(Tcl_Interp *interp){ Tcl_SetVar2(interp, "sqlite_options", "preupdate", "0", TCL_GLOBAL_ONLY); #endif +#ifdef SQLITE_ENABLE_SANDBOX_FUNCTION + Tcl_SetVar2(interp, "sqlite_options", "sandbox", "1", TCL_GLOBAL_ONLY); +#else + Tcl_SetVar2(interp, "sqlite_options", "sandbox", "0", TCL_GLOBAL_ONLY); +#endif + #ifdef SQLITE_ENABLE_SNAPSHOT Tcl_SetVar2(interp, "sqlite_options", "snapshot", "1", TCL_GLOBAL_ONLY); #else diff --git a/test/sandbox1.sql b/test/sandbox1.sql new file mode 100644 index 0000000000..3d8604a418 --- /dev/null +++ b/test/sandbox1.sql @@ -0,0 +1,48 @@ +# 2026-10-14 +# +# Compile the CLI with -DSQLITE_ENABLE_SANDBOX_FUNCTION then run this +# script to verify the sandbox() function. +# +CREATE TABLE t1(a,b,c,d); +INSERT INTO t1 VALUES(1,2,3,4),('a','b','c','d'),(9,8,7,6); +CREATE INDEX t1a ON t1(a); +CREATE INDEX t1bc ON t1(b,c); +CREATE TABLE t2(x,y,z); +INSERT INTO t2 values(1,2,3),('x','y','z'),(1,0,-1); + +.testcase 100 +SELECT * FROM t1, t2 WHERE sandbox(12345, a=x, 12346); +.check --glob 'sandbox violation' +.testcase 110 +SELECT * FROM t1, t2 WHERE sandbox(1, (SELECT 1)=x, 1); +.check --glob 'sandbox violation' +.testcase 120 +SELECT * FROM t1, t2 WHERE sandbox($abc, a=x, $abc); +.check --glob 'sandbox violation' +.testcase 130 +SELECT * FROM t1, t2 WHERE sandbox(2, a=x, 2, 4); +.check --glob 'no such function: sandbox' +.testcase 140 +SELECT * FROM t1, t2 WHERE sandbox(2, a=x) AND a=x; +.check --glob 'no such function: sandbox' +.testcase 150 +SELECT * FROM t1, t2 WHERE sandbox() AND a=x; +.check --glob 'no such function: sandbox' + +.testcase 200 +.eqp on +.mode list --colsep , --rowsep ";" +SELECT a, x FROM t1, t2 WHERE sandbox(3, b<>11 AND a=x AND y<>22, 3); +.check <