[gparted] Add unit tests for PasswordRAMStore module (#795617)



commit c6657aab9efe8cefece2017bee4bef9ece607e73
Author: Mike Fleetwood <mike fleetwood googlemail com>
Date:   Mon Oct 9 11:51:07 2017 +0100

    Add unit tests for PasswordRAMStore module (#795617)
    
    As noted in comments:
    
    1) This is white box testing because it uses implementation knowledge
       to look through the API to the internals of the password store.
    
    2) It is not currently possible to test that the passwords are zeroed
       when the store is destroyed.
       However zeroing of memory is being tested when individual passwords
       are erased.
    
    Bug 795617 - Implement opening and closing of LUKS mappings

 .gitignore                     |    1 +
 tests/Makefile.am              |   10 ++-
 tests/test_PasswordRAMStore.cc |  265 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 274 insertions(+), 2 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index 21aa5e1..77b6050 100644
--- a/.gitignore
+++ b/.gitignore
@@ -53,6 +53,7 @@ tests/*.log
 tests/*.trs
 tests/test-suite.log
 tests/test_BlockSpecial
+tests/test_PasswordRAMStore
 tests/test_PipeCapture
 tests/test_dummy
 xmldocs.make
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 1274cab..0c1cfb6 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -11,8 +11,9 @@ LDADD =  \
 
 # Programs to be built by "make check"
 check_PROGRAMS =  \
-       test_dummy         \
-       test_BlockSpecial  \
+       test_dummy             \
+       test_BlockSpecial      \
+       test_PasswordRAMStore  \
        test_PipeCapture
 
 # Test cases to be run by "make check"
@@ -25,6 +26,11 @@ test_BlockSpecial_LDADD   =  \
        $(top_builddir)/src/BlockSpecial.$(OBJEXT)  \
        $(LDADD)
 
+test_PasswordRAMStore_SOURCES = test_PasswordRAMStore.cc
+test_PasswordRAMStore_LDADD   =  \
+       $(top_builddir)/src/PasswordRAMStore.$(OBJEXT)  \
+       $(LDADD)
+
 test_PipeCapture_SOURCES  = test_PipeCapture.cc
 test_PipeCapture_LDADD    =  \
        $(top_builddir)/src/PipeCapture.$(OBJEXT)  \
diff --git a/tests/test_PasswordRAMStore.cc b/tests/test_PasswordRAMStore.cc
new file mode 100644
index 0000000..78a4360
--- /dev/null
+++ b/tests/test_PasswordRAMStore.cc
@@ -0,0 +1,265 @@
+/* Copyright (C) 2017 Mike Fleetwood
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Test PasswordRAMStore
+ *
+ * WARNING:
+ * This unit testing only calls the public API of the PasswordRAMStore so would normally
+ * be black box testing, however knowledge of the implementation is used to look through
+ * the API to the internals making this white box testing.  This is so that the hidden
+ * behaviour of zeroing password storing memory before and after use can be tested.
+ * FIXME: Can't currently test memory is zeroed when the password store is destroyed
+ * because destructor zeros memory AND removes it from the process address space.
+ *
+ * WARNING:
+ * Each test fixture would normally initialise separate resources to make the tests
+ * independent of each other.  However the password store is a single long lived shared
+ * resource.  Therefore, so that each test fixture is independent of all the others, the
+ * password store must be returned to it's original state of being empty before each
+ * fixture completes.
+ *
+ * Reference:
+ *     Google Test, Advanced Guide, Sharing Resources Between Tests in the Same Test Case
+ *     
https://github.com/google/googletest/blob/master/googletest/docs/AdvancedGuide.md#sharing-resources-between-tests-in-the-same-test-case
+ */
+
+#include "PasswordRAMStore.h"
+#include "gtest/gtest.h"
+
+#include <stdio.h>
+#include <stddef.h>
+#include <string.h>
+#include <string>
+#include <glibmm/ustring.h>
+
+namespace GParted
+{
+
+// Generate repeatable unique keys
+static const char * gen_key( unsigned int i )
+{
+       static char buf[14];
+       snprintf( buf, sizeof( buf ), "key%u", i );
+       return buf;
+}
+
+// Generate repeatable "passwords" exactly 20 characters long
+static const char * gen_passwd( unsigned int i )
+{
+       static char buf[21];
+       snprintf( buf, sizeof( buf ), "password%03u                    ", i );
+       return buf;
+}
+
+static bool mem_is_zero( const char * mem, size_t len )
+{
+       while ( len-- > 0 )
+       {
+               if ( *mem++ != '\0' )
+               {
+                       return false;
+               }
+       }
+       return true;
+}
+
+// Explicit test fixture class for common setup and sharing of the underlying password
+// store address.
+class PasswordRAMStoreTest : public ::testing::Test
+{
+protected:
+       PasswordRAMStoreTest() : looked_up_pw( NULL )  {};
+
+       static void SetUpTestCase();
+
+       static const char *  protected_mem;
+
+       std::string pw;
+       const char * looked_up_pw;
+       size_t looked_up_len;
+};
+
+// Initialise test case class static member.
+const char * PasswordRAMStoreTest::protected_mem = NULL;
+
+const size_t ProtectedMemSize = 4096;  // [Implementation knowledge: size]
+
+// Common test case initialisation discovering the underlying password store address.
+void PasswordRAMStoreTest::SetUpTestCase()
+{
+       const Glib::ustring key = "key-setup";
+       bool success = PasswordRAMStore::insert( key, "" );
+       ASSERT_TRUE( success ) << __func__ << "(): Insert \"" << key << "\" password failed";
+
+       // First password is stored at the start of the locked memory.
+       // [Implementation knowledge: locked memory layout]
+       protected_mem = PasswordRAMStore::lookup( key );
+       ASSERT_TRUE( protected_mem != NULL ) << __func__
+                                            << "(): Find \"" << key << "\" password failed";
+
+       success = PasswordRAMStore::erase( key );
+       ASSERT_TRUE( success ) << __func__ << "(): Erase \"" << key << "\" password failed";
+}
+
+TEST_F( PasswordRAMStoreTest, Initialisation )
+{
+       // Test locked memory is initialised with all zeros.
+       EXPECT_TRUE( mem_is_zero( protected_mem, ProtectedMemSize ) );
+}
+
+TEST_F( PasswordRAMStoreTest, UnknownPasswordLookup )
+{
+       // Test lookup of non-existent password fails.
+       looked_up_pw = PasswordRAMStore::lookup( "key-unknown" );
+       EXPECT_TRUE( looked_up_pw == NULL );
+}
+
+TEST_F( PasswordRAMStoreTest, UnknownPasswordErasure )
+{
+       // Test erase non-existent password fails.
+       EXPECT_FALSE( PasswordRAMStore::erase( "key-unknown" ) );
+}
+
+TEST_F( PasswordRAMStoreTest, SinglePassword )
+{
+       // Test a single password can be stored, looked up and erased (and zeroed).
+       pw = "password";
+       EXPECT_TRUE( PasswordRAMStore::insert( "key-single", pw.c_str() ) );
+
+       looked_up_pw = PasswordRAMStore::lookup( "key-single" );
+       EXPECT_STREQ( pw.c_str(), looked_up_pw );
+
+       looked_up_len = strlen( looked_up_pw );
+       EXPECT_TRUE( PasswordRAMStore::erase( "key-single" ) );
+       EXPECT_TRUE( mem_is_zero( looked_up_pw, looked_up_len ) );
+
+       EXPECT_TRUE( mem_is_zero( protected_mem, ProtectedMemSize ) );
+}
+
+TEST_F( PasswordRAMStoreTest, DuplicatePassword )
+{
+       // Test storing a password with a duplicate key fails (and single password can be
+       // erased and zeroed).
+       pw = "password";
+       EXPECT_TRUE( PasswordRAMStore::insert( "key-single", pw.c_str() ) );
+
+       looked_up_pw = PasswordRAMStore::lookup( "key-single" );
+       EXPECT_STREQ( pw.c_str(), looked_up_pw );
+
+       EXPECT_FALSE( PasswordRAMStore::insert( "key-single", pw.c_str() ) );
+
+       looked_up_len = strlen( looked_up_pw );
+       EXPECT_TRUE( PasswordRAMStore::erase( "key-single" ) );
+       EXPECT_TRUE( mem_is_zero( looked_up_pw, looked_up_len ) );
+
+       EXPECT_TRUE( mem_is_zero( protected_mem, ProtectedMemSize ) );
+}
+
+TEST_F( PasswordRAMStoreTest, OneHundredPasswordsForwards )
+{
+       // Test 100, 20 character passwords can be stored, looked up and erased (and
+       // zeroed).  Passwords are erased forwards (first stored to last stored).
+       unsigned int i;
+       for ( i = 0 ; i < 100 ; i ++ )
+       {
+               pw = gen_passwd( i );
+               EXPECT_TRUE( PasswordRAMStore::insert( gen_key(i), pw.c_str() ) );
+       }
+
+       for ( i = 0 ; i < 100 ; i ++ )
+       {
+               pw = gen_passwd( i );
+               looked_up_pw = PasswordRAMStore::lookup( gen_key(i) );
+               EXPECT_STREQ( pw.c_str(), looked_up_pw );
+       }
+
+       for ( i = 0 ; i < 100 ; i ++ )
+       {
+               pw = gen_passwd( i );
+               looked_up_pw = PasswordRAMStore::lookup( gen_key(i) );
+               looked_up_len = strlen( looked_up_pw );
+               EXPECT_TRUE( PasswordRAMStore::erase( gen_key(i) ) );
+               EXPECT_TRUE( mem_is_zero( looked_up_pw, looked_up_len ) );
+       }
+
+       EXPECT_TRUE( mem_is_zero( protected_mem, ProtectedMemSize ) );
+}
+
+TEST_F( PasswordRAMStoreTest, OneHundredPasswordsBackwards )
+{
+       // Test 100, 20 character passwords can be stored, looked up and erased (and
+       // zeroed).  Passwords are erased backwards (last stored to first stored).
+       unsigned int i;
+       for ( i = 0 ; i < 100 ; i ++ )
+       {
+               pw = gen_passwd( i );
+               EXPECT_TRUE( PasswordRAMStore::insert( gen_key(i), pw.c_str() ) );
+       }
+
+       for ( i = 0 ; i < 100 ; i ++ )
+       {
+               pw = gen_passwd( i );
+               looked_up_pw = PasswordRAMStore::lookup( gen_key(i) );
+               EXPECT_STREQ( pw.c_str(), looked_up_pw );
+       }
+
+       for ( i = 100; i-- > 0 ; )
+       {
+               pw = gen_passwd( i );
+               looked_up_pw = PasswordRAMStore::lookup( gen_key(i) );
+               looked_up_len = strlen( looked_up_pw );
+               EXPECT_TRUE( PasswordRAMStore::erase( gen_key(i) ) );
+               EXPECT_TRUE( mem_is_zero( looked_up_pw, looked_up_len ) );
+       }
+
+       EXPECT_TRUE( mem_is_zero( protected_mem, ProtectedMemSize ) );
+}
+
+TEST_F( PasswordRAMStoreTest, LongPassword )
+{
+       // Test a 4095 byte password can be stored and looked up (and erased and zeroed).
+       // [Implementation knowledge: size]
+       pw = std::string( ProtectedMemSize-1, 'X' );
+
+       EXPECT_TRUE( PasswordRAMStore::insert( "key-long", pw.c_str() ) );
+
+       looked_up_pw = PasswordRAMStore::lookup( "key-long" );
+       EXPECT_STREQ( pw.c_str(), looked_up_pw );
+
+       looked_up_len = strlen( looked_up_pw );
+       EXPECT_TRUE( PasswordRAMStore::erase( "key-long" ) );
+       EXPECT_TRUE( mem_is_zero( looked_up_pw, looked_up_len ) );
+
+       EXPECT_TRUE( mem_is_zero( protected_mem, ProtectedMemSize ) );
+}
+
+TEST_F( PasswordRAMStoreTest, TooLongPassword )
+{
+       // Test a 4096 byte password can't be stored nor looked up or erased.
+       // [Implementation knowledge: size]
+       std::string pw = std::string( ProtectedMemSize, 'X' );
+
+       EXPECT_FALSE( PasswordRAMStore::insert( "key-too-long", pw.c_str() ) );
+       EXPECT_TRUE( mem_is_zero( protected_mem, ProtectedMemSize ) );
+
+       looked_up_pw = PasswordRAMStore::lookup( "key-too-long" );
+       EXPECT_TRUE( looked_up_pw == NULL );
+
+       EXPECT_FALSE( PasswordRAMStore::erase( "key-long" ) );
+       EXPECT_TRUE( mem_is_zero( protected_mem, ProtectedMemSize ) );
+}
+
+}  // namespace GParted


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]