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
This commit is contained in:
Mike Fleetwood 2017-10-09 11:51:07 +01:00 committed by Curtis Gedak
parent 04637a3426
commit c6657aab9e
3 changed files with 274 additions and 2 deletions

1
.gitignore vendored
View file

@ -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

View file

@ -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) \

View file

@ -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