From c6657aab9efe8cefece2017bee4bef9ece607e73 Mon Sep 17 00:00:00 2001 From: Mike Fleetwood Date: Mon, 9 Oct 2017 11:51:07 +0100 Subject: [PATCH] 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(-) create mode 100644 tests/test_PasswordRAMStore.cc diff --git a/.gitignore b/.gitignore index 21aa5e16..77b6050a 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 1274cab5..0c1cfb61 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 00000000..78a43609 --- /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 . + */ + +/* 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 +#include +#include +#include +#include + +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