Repository: facebook/zstd

Problem statement:
ZSTD_DCtx_reset does not clear ZSTD_d_stableOutBuffer
**Describe the bug**
`ZSTD_DCtx_reset(_, ZSTD_reset_session_and_parameters)` does not clear the state established by `ZSTD_DCtx_setParameter(_, ZSTD_d_stableOutBuffer, 1)`.

**To Reproduce**
* Set `ZSTD_DCtx_setParameter(_, ZSTD_d_stableOutBuffer, 1)`.
* Perform a decompression session.
* Call `ZSTD_DCtx_reset(_, ZSTD_reset_session_and_parameters)` to recycle the `ZSTD_DCtx` for an unrelated session.
* Do not set `ZSTD_DCtx_setParameter(_, ZSTD_d_stableOutBuffer, 1)` this time.
* Try to perform a decompression session, using different output buffer pointers than last time.
* The error is usually `ZSTD_error_dstBuffer_wrong` or `ZSTD_error_dstSize_tooSmall` (I guess the latter is possible if the base pointer happens to match but the size does not).

**Expected behavior**
`ZSTD_DCtx_reset(_, ZSTD_reset_session_and_parameters)` should be as good as `ZSTD_freeDCtx(_)` + `ZSTD_createDCtx()`, only more efficient.

I can try preparing a test case, but hopefully the above description will be enough. I verified that the source code of `ZSTD_DCtx_reset()` does not reset `outBufferMode`.

Workaround: explicitly call `ZSTD_DCtx_setParameter(_, ZSTD_d_stableOutBuffer, 0)` after `ZSTD_DCtx_reset(_, ZSTD_reset_session_and_parameters)`.


Hints:
Thanks for the report @QrczakMK! I'll put up a fix shortly.

Patch:
diff --git a/lib/decompress/zstd_decompress.c b/lib/decompress/zstd_decompress.c
index addb0208bb9..fcf1767d2bb 100644
--- a/lib/decompress/zstd_decompress.c
+++ b/lib/decompress/zstd_decompress.c
@@ -94,11 +94,18 @@ static size_t ZSTD_startingInputLength(ZSTD_format_e format)
     return startingInputLength;
 }
 
+static void ZSTD_DCtx_resetParameters(ZSTD_DCtx* dctx)
+{
+    assert(dctx->streamStage == zdss_init);
+    dctx->format = ZSTD_f_zstd1;
+    dctx->maxWindowSize = ZSTD_MAXWINDOWSIZE_DEFAULT;
+    dctx->outBufferMode = ZSTD_obm_buffered;
+    dctx->forceIgnoreChecksum = ZSTD_d_validateChecksum;
+}
+
 static void ZSTD_initDCtx_internal(ZSTD_DCtx* dctx)
 {
-    dctx->format = ZSTD_f_zstd1;  /* ZSTD_decompressBegin() invokes ZSTD_startingInputLength() with argument dctx->format */
     dctx->staticSize  = 0;
-    dctx->maxWindowSize = ZSTD_MAXWINDOWSIZE_DEFAULT;
     dctx->ddict       = NULL;
     dctx->ddictLocal  = NULL;
     dctx->dictEnd     = NULL;
@@ -113,8 +120,7 @@ static void ZSTD_initDCtx_internal(ZSTD_DCtx* dctx)
     dctx->noForwardProgress = 0;
     dctx->oversizedDuration = 0;
     dctx->bmi2 = ZSTD_cpuid_bmi2(ZSTD_cpuid());
-    dctx->outBufferMode = ZSTD_obm_buffered;
-    dctx->forceIgnoreChecksum = ZSTD_d_validateChecksum;
+    ZSTD_DCtx_resetParameters(dctx);
     dctx->validateChecksum = 1;
 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
     dctx->dictContentEndForFuzzing = NULL;
@@ -1454,6 +1460,26 @@ static int ZSTD_dParam_withinBounds(ZSTD_dParameter dParam, int value)
     RETURN_ERROR_IF(!ZSTD_dParam_withinBounds(p, v), parameter_outOfBound, ""); \
 }
 
+size_t ZSTD_DCtx_getParameter(ZSTD_DCtx* dctx, ZSTD_dParameter param, int* value)
+{
+    switch (param) {
+        case ZSTD_d_windowLogMax:
+            *value = ZSTD_highbit32((U32)dctx->maxWindowSize);
+            return 0;
+        case ZSTD_d_format:
+            *value = dctx->format;
+            return 0;
+        case ZSTD_d_stableOutBuffer:
+            *value = dctx->outBufferMode;
+            return 0;
+        case ZSTD_d_forceIgnoreChecksum:
+            *value = dctx->forceIgnoreChecksum;
+            return 0;
+        default:;
+    }
+    RETURN_ERROR(parameter_unsupported, "");
+}
+
 size_t ZSTD_DCtx_setParameter(ZSTD_DCtx* dctx, ZSTD_dParameter dParam, int value)
 {
     RETURN_ERROR_IF(dctx->streamStage != zdss_init, stage_wrong, "");
@@ -1491,8 +1517,7 @@ size_t ZSTD_DCtx_reset(ZSTD_DCtx* dctx, ZSTD_ResetDirective reset)
       || (reset == ZSTD_reset_session_and_parameters) ) {
         RETURN_ERROR_IF(dctx->streamStage != zdss_init, stage_wrong, "");
         ZSTD_clearDict(dctx);
-        dctx->format = ZSTD_f_zstd1;
-        dctx->maxWindowSize = ZSTD_MAXWINDOWSIZE_DEFAULT;
+        ZSTD_DCtx_resetParameters(dctx);
     }
     return 0;
 }
diff --git a/lib/zstd.h b/lib/zstd.h
index 0a93ee6f674..9e570b42bc7 100644
--- a/lib/zstd.h
+++ b/lib/zstd.h
@@ -1739,6 +1739,13 @@ ZSTDLIB_API size_t ZSTD_DCtx_refPrefix_advanced(ZSTD_DCtx* dctx, const void* pre
  */
 ZSTDLIB_API size_t ZSTD_DCtx_setMaxWindowSize(ZSTD_DCtx* dctx, size_t maxWindowSize);
 
+/*! ZSTD_DCtx_getParameter() :
+ *  Get the requested decompression parameter value, selected by enum ZSTD_dParameter,
+ *  and store it into int* value.
+ * @return : 0, or an error code (which can be tested with ZSTD_isError()).
+ */
+ZSTDLIB_API size_t ZSTD_DCtx_getParameter(ZSTD_DCtx* dctx, ZSTD_dParameter param, int* value);
+
 /* ZSTD_d_format
  * experimental parameter,
  * allowing selection between ZSTD_format_e input compression formats


Test patch:
diff --git a/tests/fuzzer.c b/tests/fuzzer.c
index 9749c6dff27..8b10078accb 100644
--- a/tests/fuzzer.c
+++ b/tests/fuzzer.c
@@ -2488,6 +2488,34 @@ static int basicUnitTests(U32 const seed, double compressibility)
         ZSTD_freeDCtx(dctx);
     }
 
+    DISPLAYLEVEL(3, "test%3i : Decompression parameter reset test : ", testNb++);
+    {
+        ZSTD_DCtx* const dctx = ZSTD_createDCtx();
+        /* Attempt to future proof this to new parameters. */
+        int const maxParam = 2000;
+        int param;
+        if (ZSTD_d_experimentalParam3 > maxParam) goto _output_error;
+        for (param = 0; param < maxParam; ++param) {
+            ZSTD_dParameter dParam = (ZSTD_dParameter)param;
+            ZSTD_bounds bounds = ZSTD_dParam_getBounds(dParam);
+            int value1;
+            int value2;
+            int check;
+            if (ZSTD_isError(bounds.error))
+                continue;
+            CHECK(ZSTD_DCtx_getParameter(dctx, dParam, &value1));
+            value2 = (value1 != bounds.lowerBound) ? bounds.lowerBound : bounds.upperBound;
+            CHECK(ZSTD_DCtx_setParameter(dctx, dParam, value2));
+            CHECK(ZSTD_DCtx_getParameter(dctx, dParam, &check));
+            if (check != value2) goto _output_error;
+            CHECK(ZSTD_DCtx_reset(dctx, ZSTD_reset_parameters));
+            CHECK(ZSTD_DCtx_getParameter(dctx, dParam, &check));
+            if (check != value1) goto _output_error;
+        }
+        ZSTD_freeDCtx(dctx);
+    }
+    DISPLAYLEVEL(3, "OK \n");
+
     /* block API tests */
     {   ZSTD_CCtx* const cctx = ZSTD_createCCtx();
         ZSTD_DCtx* const dctx = ZSTD_createDCtx();