From 2d93d620dc486cbee1c4433310beea28dacafe0e Mon Sep 17 00:00:00 2001 From: drh <> Date: Wed, 14 Oct 2026 20:14:26 +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: 767597cc116079bedcf84c4d5d2e406de3182cc60f3a89da38bb82ab0c1e9444 --- manifest | 23 +++++++++--------- manifest.tags | 4 +-- manifest.uuid | 2 +- src/expr.c | 62 +++++++++++++++++++++++++++++++++++++++++------ src/sqliteInt.h | 3 ++- src/test_config.c | 6 +++++ test/sandbox1.sql | 48 ++++++++++++++++++++++++++++++++++++ tool/mkctimec.tcl | 1 + 8 files changed, 126 insertions(+), 23 deletions(-) create mode 100644 test/sandbox1.sql diff --git a/manifest b/manifest index a6f329f957..9a43776564 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Handle\stemp\striggers\sattached\sto\sdatabases\sclobbered\sby\ssqlite3_deserialize()\sin\sthe\ssame\sway\sas\sthose\sattached\sto\sDETACHed\sdatabases. -D 2026-10-05T18:11:38.016 +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:14:26.869 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -692,7 +692,7 @@ F src/date.c 61e92f1f7e2e88e1cd91e91dc69eb2b2854e7877254470f9fabd776bfac922b8 F src/dbpage.c 98c716bc5c0c70af4e7934bfcddd707f14e78b5d4cf1e0602a07b485e1af2e74 F src/dbstat.c 73362c0df0f40ad5523a6f5501224959d0976757b511299bf892313e79d14f5c F src/delete.c 1f2268d6fe3c78fc1bf794ba65d7026498b78e2342ffaf85825dedae546e6fde -F src/expr.c a7b8c6f9e8f63de44a46e76304ddbebf59c2a423ddc8c3ec31a691303e780078 +F src/expr.c 008844c5f099b2da02b0112dc00dc4c6dad670ea806e6c7bf54219f4702cb577 F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007 F src/fkey.c 931f74cec1dc8038a0217ef340c91ce147dd1bbed08dc40c47ee0ec6edfffb08 F src/func.c 03d6ae4f1b5ccdf44b68691ce7cb6353cf3aa5984fb75d69dd41b5b21031e836 @@ -746,7 +746,7 @@ F src/shell.c.in 6d65129dc4a24a38a22e044eeafffbd70cd74f95561ab05a8fb1d71ab3d31cf F src/sqlite.h.in 17f32e2b049e704f85a27a6571917c05e6f3c812ee867eb0ba5614ee9cb566cd F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479 F src/sqlite3ext.h 1b7a0ee438bb5c2896d0609c537e917d8057b3340f6ad004d2de44f03e3d3cca -F src/sqliteInt.h f71bc80528ec06ac877657f80f0171458fda685cd4a2bf1212627edcf88c8d3d +F src/sqliteInt.h a1cdabe460b385841835487d10c08d533d644097eeedf29035ce0a644cd01785 F src/sqliteLimit.h c70656b67ab5b96741a8f1c812bdd80c81f2b1c1e443d0cc3ea8c33bb1f1a092 F src/status.c 7565d63a79aa2f326339a24a0461a60096d0bd2bce711fefb50b5c89335f3592 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 @@ -765,7 +765,7 @@ F src/test_backup.c a2bfd90d2ff2511b8635507bdb30fa9b605ade19c16b533066cae3077f5b F src/test_bestindex.c d75fad21369d80910238032bcf8d9ca1f2bffda13c1ceec63bfbb7f704448b15 F src/test_blob.c 77b994e17f2c87055f44fd96c9a206c5a7155bae2cda2769af60c2f3582f962c F src/test_btree.c 28283787d32b8fa953eb77412ad0de2c9895260e4e5bd5a94b3c7411664f90d5 -F src/test_config.c e02566c2c4ee2916324ce17123a798b47663cead2de546cfbd71d8cddb46bb26 +F src/test_config.c 7a0c290cd51068c9eaa679192b8cbf93bd6cd26c57c3ad9ac0a6c5d27a6e4a57 F src/test_delete.c d0e8f6dc55cfc98a7c27c057fb88d512260564bf0b611482656c68b8f7f401ed F src/test_demovfs.c 3efa2adf4f21e10d95521721687d5ca047aea91fa62dd8cc22ac9e5a9c942383 F src/test_devsym.c 649434ed34d0b03fbd5a6b42df80f0f9a7e53f94dd1710aad5dd8831e91c4e86 @@ -1578,6 +1578,7 @@ F test/rowvaluefault.test 963ae9cdaed30a85a29668dd514e639f3556cae903ee9f172ea972 F test/rowvaluevtab.test cd9747bb3f308086944c07968f547ad6b05022e698d80b9ffbdfe09ce0b8da6f F test/rtree.test 0c8d9dd458d6824e59683c19ab2ffa9ef946f798 F test/run-wordcount.sh 891e89c4c2d16e629cd45951d4ed899ad12afc09 +F test/sandbox1.sql 35ec3792bde234d03b446a0d5551dba210d4a7bdfb3ee64cd4b7d18cdbd2a613 F test/savepoint.test 6e9804a17767f08432c7a5e738b9a8f4b891d243110b63d3a41d270d3d1378ec F test/savepoint2.test 9b8543940572a2f01a18298c3135ad0c9f4f67d7 F test/savepoint4.test c8f8159ade6d2acd9128be61e1230f1c1edc6cc0 @@ -2149,7 +2150,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 68f3ee9e2e6a06524b25d79b5b61a883053466080a39da59e0a0697f0c4d9a3b x +F tool/mkctimec.tcl f75a9f8b4561af37a45f7c2ea8c232b8a0f04f4489e5c0762f4211365942f0d5 x F tool/mkfptab.c 24ea40113f96584caca3f6dd06b4ad5adffa0f39023910cb83fa7ef2ffa9b0ba F tool/mkkeywordhash.c 82d5af1d0e677900739fba59155cddac172d8c712c2d91ab73d6e6bcb30060f0 F tool/mkmsvcmin.tcl d76c45efda1cce2d4005bcea7b8a22bb752e3256009f331120fb4fecb14ebb7a @@ -2207,9 +2208,9 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P 794e3e0fe386284327c3a58f7071087ab9d3580aae44efe835663597f74e39c2 -Q +84c773fcc2f785346fd12e8a17ac45f9068797d9629ddc367318994b6fed421b -R 777a55f2c40fa66f94e2eb9c2dcce8af -U dan -Z 386fc9eedeb6167cb33d766410e272b8 +P 872587e1567020e32c6c833b40818ba88a0b02de97b9db7b16daae08762508a8 +Q +035fd52d54a6f8f9c8276a48f466d660832d46d05a02b3ee4a41959922a4bc22 +R 5a6d9e1f449ed3cd72c95e119869a5ff +U drh +Z 8bb1081a7b6f8a6403b0d627cac3ffd7 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.tags b/manifest.tags index 8d97ab7c23..22cee6ba09 100644 --- a/manifest.tags +++ b/manifest.tags @@ -1,2 +1,2 @@ -branch branch-3.53 -tag branch-3.53 +branch branch-3.53-sandbox-func +tag branch-3.53-sandbox-func diff --git a/manifest.uuid b/manifest.uuid index 6751a1d89a..3af2cb1916 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -872587e1567020e32c6c833b40818ba88a0b02de97b9db7b16daae08762508a8 +767597cc116079bedcf84c4d5d2e406de3182cc60f3a89da38bb82ab0c1e9444 diff --git a/src/expr.c b/src/expr.c index f1d3060ac2..aeef829d9c 100644 --- a/src/expr.c +++ b/src/expr.c @@ -1162,6 +1162,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. @@ -1175,6 +1211,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 */ @@ -1182,17 +1224,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; } @@ -2847,7 +2892,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 @@ -2868,7 +2913,8 @@ int sqlite3ExprIsConstantOrFunction(Expr *p, u8 isInit){ return exprIsConst(0, p, 4+isInit); } -#ifdef SQLITE_ENABLE_CURSOR_HINTS +#if defined(SQLITE_ENABLE_CURSOR_HINTS) \ + || defined(SQLITE_ENABLE_SANDBOX_FUNCTION) /* ** Walk an expression tree. Return 1 if the expression contains a ** subquery of some kind. Return 0 if there are no subqueries. diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 6b2ed790c6..0da9e14d6d 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -5186,7 +5186,8 @@ int sqlite3ExprIsConstant(Parse*,Expr*); int sqlite3ExprIsConstantOrFunction(Expr*, u8); int sqlite3ExprIsConstantOrGroupBy(Parse*, Expr*, ExprList*); int sqlite3ExprIsSingleTableConstraint(Expr*,const SrcList*,int,int); -#ifdef SQLITE_ENABLE_CURSOR_HINTS +#if defined(SQLITE_ENABLE_CURSOR_HINTS) \ + || defined(SQLITE_ENABLE_SANDBOX_FUNCTION) int sqlite3ExprContainsSubquery(Expr*); #endif int sqlite3ExprIsInteger(const Expr*, int*, Parse*); diff --git a/src/test_config.c b/src/test_config.c index bebf8625ae..f6d6667f2d 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 <