Bug 1310944 - Implement deterministic PRNG for fuzzing mode r=franziskus
authorTim Taubert <ttaubert@mozilla.com>
Tue, 25 Oct 2016 22:35:21 +0200
changeset 12759 edc036fdbf2d8b643e94fe6035dcebdaeb9edeee
parent 12758 d405c74dfab8841f197844183efa93a3664902e6
child 12760 32fb22b38d4af965e397d21b764cc96945a4c078
push id1701
push userttaubert@mozilla.com
push dateTue, 25 Oct 2016 20:36:56 +0000
reviewersfranziskus
bugs1310944
Bug 1310944 - Implement deterministic PRNG for fuzzing mode r=franziskus Differential Revision: https://nss-dev.phacility.com/D98
build.sh
coreconf/config.gypi
external_tests/common/gtest.gypi
external_tests/pk11_gtest/manifest.mn
external_tests/pk11_gtest/pk11_gtest.gyp
external_tests/pk11_gtest/pk11_prng_unittest.cc
fuzz/warning.txt
lib/freebl/blapi.h
lib/freebl/det_rng.c
lib/freebl/det_rng.h
lib/freebl/drbg.c
lib/freebl/freebl.gyp
nss.gyp
--- a/build.sh
+++ b/build.sh
@@ -20,16 +20,17 @@ if [ -n "$CCC" ] && [ -z "$CXX" ]; then
 fi
 
 while [ $# -gt 0 ]; do
     case $1 in
         -c) CLEAN=1 ;;
         -g) REBUILD_GYP=1 ;;
         -v) VERBOSE=1 ;;
         --test) GYP_PARAMS="$GYP_PARAMS -Dtest_build=1" ;;
+        --fuzz) GYP_PARAMS="$GYP_PARAMS -Dtest_build=1 -Dfuzz=1" ;;
     esac
     shift
 done
 
 # -c = clean first
 if [ "$CLEAN" = 1 ]; then
     rm -rf "$CWD/out"
 fi
--- a/coreconf/config.gypi
+++ b/coreconf/config.gypi
@@ -47,16 +47,21 @@
         }],
         ['OS=="linux" or OS=="android"', {
           'zlib_libs%': ['<!@(<(python) <(DEPTH)/coreconf/pkg_config.py raw --libs zlib)'],
           'moz_debug_flags%': '-gdwarf-2',
           'optimize_flags%': '-O2',
           'dll_prefix': 'lib',
           'dll_suffix': 'so',
         }],
+        ['OS=="linux"', {
+          'freebl_name': 'freeblpriv3',
+        }, {
+          'freebl_name': 'freebl3',
+        }],
         ['OS=="mac"', {
           'zlib_libs%': ['-lz'],
           'use_system_sqlite%': 1,
           'moz_debug_flags%': '-gdwarf-2 -gfull',
           'optimize_flags%': '-O2',
           'dll_prefix': 'lib',
           'dll_suffix': 'dylib',
         }, {
@@ -80,29 +85,31 @@
     'nspr_lib_dir%': '<(nspr_lib_dir)',
     'nspr_include_dir%': '<(nspr_include_dir)',
     'nss_dist_obj_dir%': '<(nss_dist_obj_dir)',
     'nss_dist_dir%': '<(nss_dist_dir)',
     'use_system_sqlite%': '<(use_system_sqlite)',
     'sqlite_libs%': ['-lsqlite3'],
     'dll_prefix': '<(dll_prefix)',
     'dll_suffix': '<(dll_suffix)',
+    'freebl_name': '<(freebl_name)',
     'cc_is_clang%': '<(cc_is_clang)',
     # Some defaults
     'disable_tests%': 0,
     'disable_chachapoly%': 0,
     'disable_dbm%': 0,
     'disable_libpkix%': 0,
     'disable_werror%': 0,
     'mozilla_client%': 0,
     'moz_fold_libs%': 0,
     'moz_folded_library_name%': '',
     'ssl_enable_zlib%': 1,
     'use_asan%': 0,
     'test_build%': 0,
+    'fuzz%': 0,
   },
   'target_defaults': {
     # Settings specific to targets should go here.
     # This is mostly for linking to libraries.
     'variables': {
       'mapfile%': '',
       'test_build%': 0,
     },
@@ -274,16 +281,19 @@
               }],
             ],
           }],
           [ 'disable_werror==0 and (OS=="linux" or OS=="mac")', {
             'cflags': [
               '<!@(<(python) <(DEPTH)/coreconf/werror.py)',
             ],
           }],
+          [ 'fuzz==1', {
+            'cflags': ['-Wno-unused-function']
+          }],
           [ 'OS=="android" and mozilla_client==0', {
             'defines': [
               'NO_SYSINFO',
               'NO_FORK_CHECK',
               'ANDROID',
             ],
           }],
           [ 'OS=="mac"', {
--- a/external_tests/common/gtest.gypi
+++ b/external_tests/common/gtest.gypi
@@ -12,16 +12,21 @@
           '-lws2_32',
         ],
       }],
       ['OS=="android"', {
         'libraries': [
           '-lstdc++',
         ],
       }],
+      [ 'fuzz==1', {
+        'defines': [
+          'UNSAFE_FUZZER_MODE',
+        ],
+      }],
     ],
     'msvs_settings': {
       'VCCLCompilerTool': {
         'ExceptionHandling': 1,
         'PreprocessorDefinitions': [
           'NOMINMAX',
         ],
       },
--- a/external_tests/pk11_gtest/manifest.mn
+++ b/external_tests/pk11_gtest/manifest.mn
@@ -7,16 +7,17 @@ DEPTH      = ../..
 MODULE = nss
 
 CPPSRCS = \
       pk11_aeskeywrap_unittest.cc \
       pk11_chacha20poly1305_unittest.cc \
       pk11_export_unittest.cc \
       pk11_pbkdf2_unittest.cc \
       pk11_prf_unittest.cc \
+      pk11_prng_unittest.cc \
       pk11_rsapss_unittest.cc \
       $(NULL)
 
 INCLUDES += -I$(CORE_DEPTH)/external_tests/google_test/gtest/include \
             -I$(CORE_DEPTH)/external_tests/common
 
 REQUIRES = nspr nss libdbm gtest
 
--- a/external_tests/pk11_gtest/pk11_gtest.gyp
+++ b/external_tests/pk11_gtest/pk11_gtest.gyp
@@ -10,28 +10,25 @@
     {
       'target_name': 'pk11_gtest',
       'type': 'executable',
       'sources': [
         'pk11_aeskeywrap_unittest.cc',
         'pk11_chacha20poly1305_unittest.cc',
         'pk11_pbkdf2_unittest.cc',
         'pk11_prf_unittest.cc',
+        'pk11_prng_unittest.cc',
         'pk11_rsapss_unittest.cc',
         '<(DEPTH)/external_tests/common/gtests.cc'
       ],
       'dependencies': [
         '<(DEPTH)/exports.gyp:nss_exports',
-        '<(DEPTH)/lib/nss/nss.gyp:nss3',
-        '<(DEPTH)/lib/util/util.gyp:nssutil3',
-        '<(DEPTH)/lib/smime/smime.gyp:smime3',
-        '<(DEPTH)/lib/ssl/ssl.gyp:ssl3',
+        '<(DEPTH)/lib/freebl/freebl.gyp:<(freebl_name)',
         '<(DEPTH)/external_tests/google_test/google_test.gyp:gtest',
-        '<(DEPTH)/cmd/lib/lib.gyp:sectool'
-      ]
+      ],
     }
   ],
   'target_defaults': {
     'include_dirs': [
       '../../external_tests/google_test/gtest/include',
       '../../external_tests/common'
     ]
   },
new file mode 100644
--- /dev/null
+++ b/external_tests/pk11_gtest/pk11_prng_unittest.cc
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <memory>
+#include "blapi.h"
+#include "pk11pub.h"
+
+#include "gtest/gtest.h"
+
+namespace nss_test {
+
+class PK11PrngTest : public ::testing::Test {};
+
+#ifdef UNSAFE_FUZZER_MODE
+
+// Test that two consecutive calls to the RNG return two distinct values.
+TEST_F(PK11PrngTest, Fuzz_DetPRNG) {
+  std::vector<uint8_t> rnd1(2048, 0);
+  std::vector<uint8_t> rnd2(2048, 0);
+
+  SECStatus rv = PK11_GenerateRandom(rnd1.data(), rnd1.size());
+  EXPECT_EQ(rv, SECSuccess);
+
+  rv = PK11_GenerateRandom(rnd2.data(), rnd2.size());
+  EXPECT_EQ(rv, SECSuccess);
+
+  EXPECT_NE(rnd1, rnd2);
+}
+
+// Test that two consecutive calls to the RNG return two equal values
+// when the RNG's internal state is reset before each call.
+TEST_F(PK11PrngTest, Fuzz_DetPRNG_Reset) {
+  std::vector<uint8_t> rnd1(2048, 0);
+  std::vector<uint8_t> rnd2(2048, 0);
+
+  RNG_ResetForFuzzing();
+
+  SECStatus rv = PK11_GenerateRandom(rnd1.data(), rnd1.size());
+  EXPECT_EQ(rv, SECSuccess);
+
+  RNG_ResetForFuzzing();
+
+  rv = PK11_GenerateRandom(rnd2.data(), rnd2.size());
+  EXPECT_EQ(rv, SECSuccess);
+
+  EXPECT_EQ(rnd1, rnd2);
+}
+
+// Test that the RNG's internal state progresses in a consistent manner.
+TEST_F(PK11PrngTest, Fuzz_DetPRNG_StatefulReset) {
+  std::vector<uint8_t> rnd1(2048, 0);
+  std::vector<uint8_t> rnd2(2048, 0);
+
+  RNG_ResetForFuzzing();
+
+  SECStatus rv = PK11_GenerateRandom(rnd1.data(), rnd1.size() - 1024);
+  EXPECT_EQ(rv, SECSuccess);
+
+  rv = PK11_GenerateRandom(rnd1.data() + 1024, rnd1.size() - 1024);
+  EXPECT_EQ(rv, SECSuccess);
+
+  RNG_ResetForFuzzing();
+
+  rv = PK11_GenerateRandom(rnd2.data(), rnd2.size() - 1024);
+  EXPECT_EQ(rv, SECSuccess);
+
+  rv = PK11_GenerateRandom(rnd2.data() + 1024, rnd2.size() - 1024);
+  EXPECT_EQ(rv, SECSuccess);
+
+  EXPECT_EQ(rnd1, rnd2);
+}
+
+#endif
+
+}  // namespace nss_test
new file mode 100644
--- /dev/null
+++ b/fuzz/warning.txt
@@ -0,0 +1,15 @@
+
+##############################################
+##                                          ##
+##  WARNING: You're building with -Dfuzz=1  ##
+##                                          ##
+##  This means:                             ##
+##                                          ##
+##   * Your PRNG is DETERMINISTIC.          ##
+##   * TLS transcripts are PLAINTEXT.       ##
+##   * TLS signature checks are DISABLED.   ##
+##                                          ##
+##  Thank you for fuzzing!                  ##
+##                                          ##
+##############################################
+
--- a/lib/freebl/blapi.h
+++ b/lib/freebl/blapi.h
@@ -1424,16 +1424,18 @@ extern SECStatus RNG_RNGInit(void);
 extern SECStatus RNG_RandomUpdate(const void *data, size_t bytes);
 
 /*
 ** Generate some random bytes, using the global random number generator
 ** object.
 */
 extern SECStatus RNG_GenerateGlobalRandomBytes(void *dest, size_t len);
 
+extern SECStatus RNG_ResetForFuzzing(void);
+
 /* Destroy the global RNG context.  After a call to RNG_RNGShutdown()
 ** a call to RNG_RNGInit() is required in order to use the generator again,
 ** along with seed data (see the comment above RNG_RNGInit()).
 */
 extern void RNG_RNGShutdown(void);
 
 extern void RNG_SystemInfoForRNG(void);
 
new file mode 100644
--- /dev/null
+++ b/lib/freebl/det_rng.c
@@ -0,0 +1,67 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "blapi.h"
+#include "blapit.h"
+#include "chacha20.h"
+#include "nssilock.h"
+#include "seccomon.h"
+#include "secerr.h"
+
+static unsigned long globalNumCalls = 0;
+
+SECStatus
+prng_ResetForFuzzing(PZLock *rng_lock)
+{
+    /* Check for a valid RNG lock. */
+    PORT_Assert(rng_lock != NULL);
+    if (rng_lock == NULL) {
+        PORT_SetError(SEC_ERROR_INVALID_ARGS);
+        return SECFailure;
+    }
+
+    /* --- LOCKED --- */
+    PZ_Lock(rng_lock);
+    globalNumCalls = 0;
+    PZ_Unlock(rng_lock);
+    /* --- UNLOCKED --- */
+
+    return SECSuccess;
+}
+
+SECStatus
+prng_GenerateDeterministicRandomBytes(PZLock *rng_lock, void *dest, size_t len)
+{
+    static const uint8_t key[32];
+    uint8_t nonce[12] = { 0 };
+
+    /* Check for a valid RNG lock. */
+    PORT_Assert(rng_lock != NULL);
+    if (rng_lock == NULL) {
+        PORT_SetError(SEC_ERROR_INVALID_ARGS);
+        return SECFailure;
+    }
+
+    /* --- LOCKED --- */
+    PZ_Lock(rng_lock);
+
+    memcpy(nonce, &globalNumCalls, sizeof(globalNumCalls));
+    globalNumCalls++;
+
+    ChaCha20Poly1305Context *cx =
+        ChaCha20Poly1305_CreateContext(key, sizeof(key), 16);
+    if (!cx) {
+        PORT_SetError(SEC_ERROR_NO_MEMORY);
+        PZ_Unlock(rng_lock);
+        return SECFailure;
+    }
+
+    memset(dest, 0, len);
+    ChaCha20XOR(dest, dest, len, key, nonce, 0);
+    ChaCha20Poly1305_DestroyContext(cx, PR_TRUE);
+
+    PZ_Unlock(rng_lock);
+    /* --- UNLOCKED --- */
+    return SECSuccess;
+}
new file mode 100644
--- /dev/null
+++ b/lib/freebl/det_rng.h
@@ -0,0 +1,12 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __det_rng_h_
+#define __det_rng_h_
+
+SECStatus prng_ResetForFuzzing(PZLock *rng_lock);
+SECStatus prng_GenerateDeterministicRandomBytes(PZLock *rng_lock, void *dest,
+                                                size_t len);
+
+#endif /* __det_rng_h_ */
--- a/lib/freebl/drbg.c
+++ b/lib/freebl/drbg.c
@@ -15,16 +15,20 @@
 #include "blapii.h"
 #include "nssilock.h"
 #include "secitem.h"
 #include "sha_fast.h"
 #include "sha256.h"
 #include "secrng.h" /* for RNG_SystemRNG() */
 #include "secmpi.h"
 
+#ifdef UNSAFE_FUZZER_MODE
+#include "det_rng.h"
+#endif
+
 /* PRNG_SEEDLEN defined in NIST SP 800-90 section 10.1
  * for SHA-1, SHA-224, and SHA-256 it's 440 bits.
  * for SHA-384 and SHA-512 it's 888 bits */
 #define PRNG_SEEDLEN (440 / PR_BITS_PER_BYTE)
 #define PRNG_MAX_ADDITIONAL_BYTES PR_INT64(0x100000000)
 /* 2^35 bits or 2^32 bytes */
 #define PRNG_MAX_REQUEST_SIZE 0x10000             /* 2^19 bits or 2^16 bytes */
 #define PRNG_ADDITONAL_DATA_CACHE_SIZE (8 * 1024) /* must be less than          \
@@ -645,17 +649,31 @@ prng_GenerateGlobalRandomBytes(RNGContex
 
 /*
 ** Generate some random bytes, using the global random number generator
 ** object.
 */
 SECStatus
 RNG_GenerateGlobalRandomBytes(void *dest, size_t len)
 {
+#ifdef UNSAFE_FUZZER_MODE
+    return prng_GenerateDeterministicRandomBytes(globalrng->lock, dest, len);
+#else
     return prng_GenerateGlobalRandomBytes(globalrng, dest, len);
+#endif
+}
+
+SECStatus
+RNG_ResetForFuzzing(void)
+{
+#ifdef UNSAFE_FUZZER_MODE
+    return prng_ResetForFuzzing(globalrng->lock);
+#else
+    return SECFailure;
+#endif
 }
 
 void
 RNG_RNGShutdown(void)
 {
     /* check for a valid global RNG context */
     PORT_Assert(globalrng != NULL);
     if (globalrng == NULL) {
--- a/lib/freebl/freebl.gyp
+++ b/lib/freebl/freebl.gyp
@@ -42,16 +42,17 @@
         'arcfive.c',
         'arcfour.c',
         'camellia.c',
         'chacha20poly1305.c',
         'ctr.c',
         'cts.c',
         'des.c',
         'desblapi.c',
+        'det_rng.c',
         'dh.c',
         'drbg.c',
         'dsa.c',
         'ec.c',
         'ecdecode.c',
         'ecl/ec_naf.c',
         'ecl/ecl.c',
         'ecl/ecl_curve.c',
@@ -208,16 +209,21 @@
               # not x64
               'sources': [
                 'chacha20.c',
                 'poly1305.c',
               ],
             }],
           ],
         }],
+        [ 'fuzz==1', {
+          'defines': [
+            'UNSAFE_FUZZER_MODE',
+          ],
+        }],
         [ 'OS=="mac"', {
           'conditions': [
             [ 'target_arch=="ia32"', {
               'sources': [
                 'mpi/mpi_sse2.s',
               ],
               'defines': [
                 'MP_USE_UINT_DIGIT',
@@ -388,17 +394,10 @@
       [ 'OS=="mac"', {
       }],
       [ 'OS=="win"', {
       }],
     ],
   },
   'variables': {
     'module': 'nss',
-    'conditions': [
-      [ 'OS=="linux"', {
-        'freebl_name': 'freeblpriv3',
-      }, {
-        'freebl_name': 'freebl3',
-      }],
-    ],
   }
 }
--- a/nss.gyp
+++ b/nss.gyp
@@ -225,10 +225,27 @@
               'dependencies': [
                 'cmd/pkix-errcodes/pkix-errcodes.gyp:pkix-errcodes',
               ],
             }],
           ],
         },
       ],
     }],
+    [ 'fuzz==1', {
+      'targets': [
+        {
+          'target_name': 'fuzz',
+          'type': 'none',
+          'actions': [
+            {
+              'action_name': 'warn_fuzz',
+              'action': ['cat', 'fuzz/warning.txt'],
+              'inputs': ['fuzz/warning.txt'],
+              'ninja_use_console': 1,
+              'outputs': ['dummy'],
+            }
+          ],
+        },
+      ],
+    }],
   ],
 }