Fossil

Check-in [d1bb87bcfd]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:The taint markings and detection now appears to be working.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | th1-taint
Files: files | file ages | folders
SHA3-256: d1bb87bcfdd4ad327bf23aa141c24417c10585b62a3267d65561ae9955efeb4e
User & Date: drh 2025-04-19 18:43:03.708
Context
2025-04-19
19:08
Mark some TH1 inputs that can be controlled by the user as tainted. check-in: 2742682720 user: drh tags: th1-taint
18:43
The taint markings and detection now appears to be working. check-in: d1bb87bcfd user: drh tags: th1-taint
16:55
Experimental changes to TH1 to try to make it resistant to coding errors that could lead to XSS or SQL injection attacks. check-in: b0b4492480 user: drh tags: th1-taint
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/printf.c.
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
        fprintf(out, "%s=%s\n", azEnv[i], p);
        fossil_path_free(p);
      }else if( (z = P(azEnv[i]))!=0 && z[0]!=0 ){
        fprintf(out, "%s=%s\n", azEnv[i], z);
      }
    }
  }
  fclose(out);
}

/*
** The following variable becomes true while processing a fatal error
** or a panic.  If additional "recursive-fatal" errors occur while
** shutting down, the recursive errors are silently ignored.
*/







|







1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
        fprintf(out, "%s=%s\n", azEnv[i], p);
        fossil_path_free(p);
      }else if( (z = P(azEnv[i]))!=0 && z[0]!=0 ){
        fprintf(out, "%s=%s\n", azEnv[i], z);
      }
    }
  }
  if( out!=stderr ) fclose(out);
}

/*
** The following variable becomes true while processing a fatal error
** or a panic.  If additional "recursive-fatal" errors occur while
** shutting down, the recursive errors are silently ignored.
*/
Changes to src/th.c.
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
        thBufferWrite(interp, &output, zRes, nRes);
        i += (nGet-1);
      }
    }
  }

  if( rc==TH_OK ){
    Th_SetResult(interp, output.zBuf, output.nBuf);
  }
  thBufferFree(interp, &output);
  return rc;
}

/*
** Return true if one of the following is true of the buffer pointed







|







778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
        thBufferWrite(interp, &output, zRes, nRes);
        i += (nGet-1);
      }
    }
  }

  if( rc==TH_OK ){
    Th_SetResult(interp, output.zBuf, output.nBuf|output.bTaint);
  }
  thBufferFree(interp, &output);
  return rc;
}

/*
** Return true if one of the following is true of the buffer pointed
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879

  while( nInput>0 ){
    const char *zWord;
    int nWord;

    thNextSpace(interp, zInput, nInput, &nWord);
    zInput += nWord;
    nInput = nList-(zInput-zList);

    if( TH_OK!=(rc = thNextWord(interp, zInput, nInput, &nWord, 0))
     || TH_OK!=(rc = thSubstWord(interp, zInput, nWord))
    ){
      goto finish;
    }
    zInput = &zInput[nWord];
    nInput = nList-(zInput-zList);
    if( nWord>0 ){
      zWord = Th_GetResult(interp, &nWord);
      thBufferWrite(interp, &strbuf, zWord, nWord);
      thBufferAddChar(interp, &strbuf, 0);
      thBufferWrite(interp, &lenbuf, &nWord, sizeof(int));
      nCount++;
    }







|






|
|







857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879

  while( nInput>0 ){
    const char *zWord;
    int nWord;

    thNextSpace(interp, zInput, nInput, &nWord);
    zInput += nWord;
    nInput = TH1_LEN(nList)-(zInput-zList);

    if( TH_OK!=(rc = thNextWord(interp, zInput, nInput, &nWord, 0))
     || TH_OK!=(rc = thSubstWord(interp, zInput, nWord))
    ){
      goto finish;
    }
    zInput = &zInput[TH1_LEN(nWord)];
    nInput = TH1_LEN(nList)-(zInput-zList);
    if( nWord>0 ){
      zWord = Th_GetResult(interp, &nWord);
      thBufferWrite(interp, &strbuf, zWord, nWord);
      thBufferAddChar(interp, &strbuf, 0);
      thBufferWrite(interp, &lenbuf, &nWord, sizeof(int));
      nCount++;
    }
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
    );
    anElem = (int *)&azElem[nCount];
    zElem = (char *)&anElem[nCount];
    th_memcpy(anElem, lenbuf.zBuf, lenbuf.nBuf);
    th_memcpy(zElem, strbuf.zBuf, strbuf.nBuf);
    for(i=0; i<nCount;i++){
      azElem[i] = zElem;
      zElem += (anElem[i] + 1);
    }
    *pazElem = azElem;
    *panElem = anElem;
  }
  if( pnCount ){
    *pnCount = nCount;
  }







|







892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
    );
    anElem = (int *)&azElem[nCount];
    zElem = (char *)&anElem[nCount];
    th_memcpy(anElem, lenbuf.zBuf, lenbuf.nBuf);
    th_memcpy(zElem, strbuf.zBuf, strbuf.nBuf);
    for(i=0; i<nCount;i++){
      azElem[i] = zElem;
      zElem += (TH1_LEN(anElem[i]) + 1);
    }
    *pazElem = azElem;
    *panElem = anElem;
  }
  if( pnCount ){
    *pnCount = nCount;
  }
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
  Th_Interp *interp,       /* Report error here, if an error is reported */
  const char *zWhere,      /* Where the tainted string appears */
  const char *zStr,        /* The tainted string */
  int nStr                 /* Length of the tainted string */
){
  nStr = TH1_LEN(nStr);
  if( nStr>0 ){
    fossil_errorlog("warning: tainted %s: \"%.s\"", zWhere, nStr, zStr);
  }else{
    fossil_errorlog("warning: tainted %s", zWhere);
  }
  return 0;
}

/*
** Evaluate the th1 script contained in the string (zProgram, nProgram)
** in the current stack frame.
*/
static int thEvalLocal(Th_Interp *interp, const char *zProgram, int nProgram){
  int rc = TH_OK;
  const char *zInput = zProgram;
  int nInput = nProgram;

  if( TH1_TAINTED(nProgram)
   && Th_ReportTaint(interp, "script", zProgram, nProgram)
  ){
    return TH_ERROR;
  }
  while( rc==TH_OK && nInput ){







|













|







924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
  Th_Interp *interp,       /* Report error here, if an error is reported */
  const char *zWhere,      /* Where the tainted string appears */
  const char *zStr,        /* The tainted string */
  int nStr                 /* Length of the tainted string */
){
  nStr = TH1_LEN(nStr);
  if( nStr>0 ){
    fossil_errorlog("warning: tainted %s: \"%.*s\"", zWhere, nStr, zStr);
  }else{
    fossil_errorlog("warning: tainted %s", zWhere);
  }
  return 0;
}

/*
** Evaluate the th1 script contained in the string (zProgram, nProgram)
** in the current stack frame.
*/
static int thEvalLocal(Th_Interp *interp, const char *zProgram, int nProgram){
  int rc = TH_OK;
  const char *zInput = zProgram;
  int nInput = TH1_LEN(nProgram);

  if( TH1_TAINTED(nProgram)
   && Th_ReportTaint(interp, "script", zProgram, nProgram)
  ){
    return TH_ERROR;
  }
  while( rc==TH_OK && nInput ){
1102
1103
1104
1105
1106
1107
1108


1109
1110
1111
1112
1113
1114
1115
  if( !interp->pFrame ){
    rc = TH_ERROR;
  }else{
    int nInput = nProgram;

    if( nInput<0 ){
      nInput = th_strlen(zProgram);


    }
    rc = thEvalLocal(interp, zProgram, nInput);
  }

  interp->pFrame = pSavedFrame;
  return rc;
}







>
>







1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
  if( !interp->pFrame ){
    rc = TH_ERROR;
  }else{
    int nInput = nProgram;

    if( nInput<0 ){
      nInput = th_strlen(zProgram);
    }else{
      nInput = TH1_LEN(nInput);
    }
    rc = thEvalLocal(interp, zProgram, nInput);
  }

  interp->pFrame = pSavedFrame;
  return rc;
}
Changes to src/th_main.c.
380
381
382
383
384
385
386
387
388
389
390
391
392


393
394
395

396
397
398
399
400
401
402
403
404
405
406
407
408
** Escape all characters with special meaning to HTML if the encode
** parameter is true, with the exception that that flag is ignored if
** g.th1Flags has the TH_INIT_NO_ENCODE flag.
**
** If pOut is NULL and the global pThOut is not then that blob
** is used for output.
*/
static void sendText(Blob * pOut, const char *z, int n, int encode){
  if(0==pOut && pThOut!=0){
    pOut = pThOut;
  }
  if(TH_INIT_NO_ENCODE & g.th1Flags){
    encode = 0;


    if( TH1_TAINTED(n) && Th_ReportTaint(0, "output string", z, n) ){
      return;
    }

  }
  if( enableOutput && n ){
    if( n<0 ){
      n = strlen(z);
    }else{
      n = TH1_LEN(n);
    }
    if( encode ){
      z = htmlize(z, n);
      n = strlen(z);
    }
    if(pOut!=0){
      blob_append(pOut, z, n);







|





>
>
|


>




<
<







380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402


403
404
405
406
407
408
409
** Escape all characters with special meaning to HTML if the encode
** parameter is true, with the exception that that flag is ignored if
** g.th1Flags has the TH_INIT_NO_ENCODE flag.
**
** If pOut is NULL and the global pThOut is not then that blob
** is used for output.
*/
static void sendText(Blob *pOut, const char *z, int n, int encode){
  if(0==pOut && pThOut!=0){
    pOut = pThOut;
  }
  if(TH_INIT_NO_ENCODE & g.th1Flags){
    encode = 0;
  }
  if( encode==0 && n>0 && TH1_TAINTED(n) ){
    if( Th_ReportTaint(0, "output string", z, n) ){
      return;
    }
    n = TH1_LEN(n);
  }
  if( enableOutput && n ){
    if( n<0 ){
      n = strlen(z);


    }
    if( encode ){
      z = htmlize(z, n);
      n = strlen(z);
    }
    if(pOut!=0){
      blob_append(pOut, z, n);
1863
1864
1865
1866
1867
1868
1869





































1870
1871
1872
1873
1874
1875
1876
  char zUTime[50];
  fossil_cpu_times(0, &x);
  sqlite3_snprintf(sizeof(zUTime), zUTime, "%llu", x);
  Th_SetResult(interp, zUTime, -1);
  return TH_OK;
}







































/*
** TH1 command: randhex  N
**
** Return N*2 random hexadecimal digits with N<50.  If N is omitted,
** use a value of 10.
*/







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
  char zUTime[50];
  fossil_cpu_times(0, &x);
  sqlite3_snprintf(sizeof(zUTime), zUTime, "%llu", x);
  Th_SetResult(interp, zUTime, -1);
  return TH_OK;
}

/*
** TH1 command: taint STRING
**
** Return a copy of STRING that is marked as tainted.
*/
static int taintCmd(
  Th_Interp *interp,
  void *p,
  int argc,
  const char **argv,
  int *argl
){
  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "STRING");
  }
  Th_SetResult(interp, argv[1], TH1_ADD_TAINT(argl[1]));
  return TH_OK;
}

/*
** TH1 command: untaint STRING
**
** Return a copy of STRING that is marked as untainted.
*/
static int untaintCmd(
  Th_Interp *interp,
  void *p,
  int argc,
  const char **argv,
  int *argl
){
  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "STRING");
  }
  Th_SetResult(interp, argv[1], TH1_LEN(argl[1]));
  return TH_OK;
}

/*
** TH1 command: randhex  N
**
** Return N*2 random hexadecimal digits with N<50.  If N is omitted,
** use a value of 10.
*/
1938
1939
1940
1941
1942
1943
1944

1945

1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960

1961
1962


1963
1964
1965
1966
1967
1968
1969
  const char *zTail;
  int n, i;
  int res = TH_OK;
  int nVar;
  char *zErr = 0;
  int noComplain = 0;


  if( argc>3 && argl[1]==11 && strncmp(argv[1], "-nocomplain", 11)==0 ){

    argc--;
    argv++;
    argl++;
    noComplain = 1;
  }
  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "query SQL CODE");
  }
  if( g.db==0 ){
    if( noComplain ) return TH_OK;
    Th_ErrorMessage(interp, "database is not open", 0, 0);
    return TH_ERROR;
  }
  zSql = argv[1];
  nSql = argl[1];

  if( TH1_TAINTED(nSql) && Th_ReportTaint(interp,"query SQL",zSql,nSql) ){
    return TH_ERROR;


  }

  while( res==TH_OK && nSql>0 ){
    zErr = 0;
    report_restrict_sql(&zErr);
    g.dbIgnoreErrors++;
    rc = sqlite3_prepare_v2(g.db, argv[1], TH1_LEN(argl[1]), &pStmt, &zTail);







>
|
>















>
|
|
>
>







1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
  const char *zTail;
  int n, i;
  int res = TH_OK;
  int nVar;
  char *zErr = 0;
  int noComplain = 0;

  if( argc>3 && TH1_LEN(argl[1])==11
   && strncmp(argv[1], "-nocomplain", 11)==0
  ){
    argc--;
    argv++;
    argl++;
    noComplain = 1;
  }
  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "query SQL CODE");
  }
  if( g.db==0 ){
    if( noComplain ) return TH_OK;
    Th_ErrorMessage(interp, "database is not open", 0, 0);
    return TH_ERROR;
  }
  zSql = argv[1];
  nSql = argl[1];
  if( TH1_TAINTED(nSql) ){
    if( Th_ReportTaint(interp,"query SQL",zSql,nSql) ){
      return TH_ERROR;
    }
    nSql = TH1_LEN(nSql);
  }

  while( res==TH_OK && nSql>0 ){
    zErr = 0;
    report_restrict_sql(&zErr);
    g.dbIgnoreErrors++;
    rc = sqlite3_prepare_v2(g.db, argv[1], TH1_LEN(argl[1]), &pStmt, &zTail);
2406
2407
2408
2409
2410
2411
2412

2413
2414
2415

2416
2417
2418
2419
2420
2421
2422
    {"searchable",    searchableCmd,        0},
    {"setParameter",  setParameterCmd,      0},
    {"setting",       settingCmd,           0},
    {"styleFooter",   styleFooterCmd,       0},
    {"styleHeader",   styleHeaderCmd,       0},
    {"styleScript",   styleScriptCmd,       0},
    {"submenu",       submenuCmd,           0},

    {"tclReady",      tclReadyCmd,          0},
    {"trace",         traceCmd,             0},
    {"stime",         stimeCmd,             0},

    {"unversioned",   unversionedCmd,       0},
    {"utime",         utimeCmd,             0},
    {"verifyCsrf",    verifyCsrfCmd,        0},
    {"verifyLogin",   verifyLoginCmd,       0},
    {"wiki",          wikiCmd,              (void*)&aFlags[0]},
    {"wiki_assoc",    wikiAssocCmd,         0},
    {0, 0, 0}







>



>







2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
    {"searchable",    searchableCmd,        0},
    {"setParameter",  setParameterCmd,      0},
    {"setting",       settingCmd,           0},
    {"styleFooter",   styleFooterCmd,       0},
    {"styleHeader",   styleHeaderCmd,       0},
    {"styleScript",   styleScriptCmd,       0},
    {"submenu",       submenuCmd,           0},
    {"taint",         taintCmd,             0},
    {"tclReady",      tclReadyCmd,          0},
    {"trace",         traceCmd,             0},
    {"stime",         stimeCmd,             0},
    {"untaint",       untaintCmd,           0},
    {"unversioned",   unversionedCmd,       0},
    {"utime",         utimeCmd,             0},
    {"verifyCsrf",    verifyCsrfCmd,        0},
    {"verifyLogin",   verifyLoginCmd,       0},
    {"wiki",          wikiCmd,              (void*)&aFlags[0]},
    {"wiki_assoc",    wikiAssocCmd,         0},
    {0, 0, 0}
3011
3012
3013
3014
3015
3016
3017

3018
3019
3020
3021
3022
3023
3024
    g.useLocalauth = 1;
  }
  if( find_option("set-user-caps", 0, 0)!=0 ){
    const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
    login_set_capabilities(zCap ? zCap : "sx", 0);
    g.useLocalauth = 1;
  }

  verify_all_options();
  if( g.argc<3 ){
    usage("FILE");
  }
  blob_zero(&in);
  blob_read_from_file(&in, g.argv[2], ExtFILE);
  Th_Render(blob_str(&in));







>







3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
    g.useLocalauth = 1;
  }
  if( find_option("set-user-caps", 0, 0)!=0 ){
    const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
    login_set_capabilities(zCap ? zCap : "sx", 0);
    g.useLocalauth = 1;
  }
  db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
  verify_all_options();
  if( g.argc<3 ){
    usage("FILE");
  }
  blob_zero(&in);
  blob_read_from_file(&in, g.argv[2], ExtFILE);
  Th_Render(blob_str(&in));
3063
3064
3065
3066
3067
3068
3069

3070
3071
3072
3073
3074
3075
3076
    g.useLocalauth = 1;
  }
  if( find_option("set-user-caps", 0, 0)!=0 ){
    const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
    login_set_capabilities(zCap ? zCap : "sx", 0);
    g.useLocalauth = 1;
  }

  verify_all_options();
  if( g.argc!=3 ){
    usage("script");
  }
  if(file_isfile(g.argv[2], ExtFILE)){
    blob_read_from_file(&code, g.argv[2], ExtFILE);
    zCode = blob_str(&code);







>







3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
    g.useLocalauth = 1;
  }
  if( find_option("set-user-caps", 0, 0)!=0 ){
    const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
    login_set_capabilities(zCap ? zCap : "sx", 0);
    g.useLocalauth = 1;
  }
  db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
  verify_all_options();
  if( g.argc!=3 ){
    usage("script");
  }
  if(file_isfile(g.argv[2], ExtFILE)){
    blob_read_from_file(&code, g.argv[2], ExtFILE);
    zCode = blob_str(&code);