[go: up one dir, main page]

Skip to content

Commit

Permalink
Add tests for prot-based security + bugfixes
Browse files Browse the repository at this point in the history
  • Loading branch information
jvanstraten committed Sep 3, 2019
1 parent 7d51cf8 commit b8cf2f9
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 10 deletions.
171 changes: 171 additions & 0 deletions tests/integration/test_security.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
"""Test prot-based security."""

from unittest import TestCase
from ..testbench import RegisterFileTestbench

class TestSecurity(TestCase):
"""Test prot-based security."""

def test_basic(self):
"""test denying access based on prot"""
rft = RegisterFileTestbench({
'metadata': {'name': 'test'},
'fields': [
{
'address': 0,
'bitrange': '15..0',
'name': 'a',
'behavior': 'control',
'write-allow-user': False,
'write-allow-nonsecure': False,
'write-allow-instruction': False,
'read-allow-secure': False,
'read-allow-data': False,
},
{
'address': 0,
'bitrange': '31..16',
'name': 'b',
'behavior': 'control',
'write-allow-privileged': False,
'write-allow-nonsecure': False,
'write-allow-instruction': False,
'read-allow-user': False,
},
]})
self.assertEqual(rft.ports, (
'bus',
'f_a_o.data',
'f_b_o.data',
))
with rft as objs:
objs.bus.write(0, 0xA000A000, prot='000') # b
objs.bus.write(0, 0xA001A001, prot='001') # a
with self.assertRaisesRegex(ValueError, 'decode'):
objs.bus.write(0, 0xA010A010, prot='010')
with self.assertRaisesRegex(ValueError, 'decode'):
objs.bus.write(0, 0xA011A011, prot='011')
with self.assertRaisesRegex(ValueError, 'decode'):
objs.bus.write(0, 0xA100A100, prot='100')
with self.assertRaisesRegex(ValueError, 'decode'):
objs.bus.write(0, 0xA101A101, prot='101')
with self.assertRaisesRegex(ValueError, 'decode'):
objs.bus.write(0, 0xA110A110, prot='110')
with self.assertRaisesRegex(ValueError, 'decode'):
objs.bus.write(0, 0xA111A111, prot='111')

with self.assertRaisesRegex(ValueError, 'decode'):
objs.bus.read(0, prot='000')
self.assertEqual(objs.bus.read(0, prot='001'), 0xA0000000)
with self.assertRaisesRegex(ValueError, 'decode'):
objs.bus.read(0, prot='010')
self.assertEqual(objs.bus.read(0, prot='011'), 0xA0000000)
with self.assertRaisesRegex(ValueError, 'decode'):
objs.bus.read(0, prot='100')
self.assertEqual(objs.bus.read(0, prot='101'), 0xA0000000)
self.assertEqual(objs.bus.read(0, prot='110'), 0x0000A001)
self.assertEqual(objs.bus.read(0, prot='111'), 0xA000A001)

def test_hardening(self):
"""test hardening"""
rft = RegisterFileTestbench({
'metadata': {'name': 'test'},
#'features': {'insecure': True},
'fields': [
{
'address': 0,
'bitrange': '63..0',
'name': 'secret',
'behavior': 'control',
'write-allow-user': False,
'read-allow-user': False,
},
{
'address': 8,
'bitrange': '63..0',
'name': 'x',
'behavior': 'control',
},
]})
self.assertEqual(rft.ports, (
'bus',
'f_secret_o.data',
'f_x_o.data',
))
with rft as objs:
# Privileged write.
objs.bus.write(0, 0x11223344, prot='001')
objs.bus.write(4, 0x55667788, prot='001')

# Make sure that the write holding register is properly cleared.
objs.bus.write(12, 0, strb='0000')
self.assertEqual(objs.bus.read(8), 0)
self.assertEqual(objs.bus.read(12), 0)

# Privileged read.
self.assertEqual(objs.bus.read(0, prot='001'), 0x11223344)
self.assertEqual(objs.bus.read(4, prot='001'), 0x55667788)

# Make sure that the read holding register is properly cleared.
try:
self.assertEqual(objs.bus.read(12), 0)
except ValueError:
pass

# Try to change the secret by interrupting a privileged write.
objs.bus.write(0, 0x22334455, prot='001')
with self.assertRaisesRegex(ValueError, 'slave'):
objs.bus.write(0, 0xDEADC0DE)
objs.bus.write(4, 0x66778899, prot='001')
self.assertEqual(objs.bus.read(0, prot='001'), 0x22334455)
self.assertEqual(objs.bus.read(4, prot='001'), 0x66778899)

# Try to read the secret by interrupting a privileged read.
self.assertEqual(objs.bus.read(0, prot='001'), 0x22334455)
with self.assertRaisesRegex(ValueError, 'slave'):
objs.bus.read(4)
self.assertEqual(objs.bus.read(4, prot='001'), 0x66778899)

def test_error(self):
"""test prot-related error messages"""
with self.assertRaisesRegex(Exception, 'cannot deny both user and privileged accesses'):
RegisterFileTestbench({
'metadata': {'name': 'test'},
'fields': [
{
'address': 0,
'bitrange': '15..0',
'name': 'a',
'behavior': 'control',
'write-allow-user': False,
'write-allow-privileged': False,
},
]})

with self.assertRaisesRegex(Exception, 'cannot deny both secure and nonsecure accesses'):
RegisterFileTestbench({
'metadata': {'name': 'test'},
'fields': [
{
'address': 0,
'bitrange': '15..0',
'name': 'a',
'behavior': 'control',
'write-allow-secure': False,
'write-allow-nonsecure': False,
},
]})

with self.assertRaisesRegex(Exception, 'cannot deny both data and instruction accesses'):
RegisterFileTestbench({
'metadata': {'name': 'test'},
'fields': [
{
'address': 0,
'bitrange': '15..0',
'name': 'a',
'behavior': 'control',
'read-allow-data': False,
'read-allow-instruction': False,
},
]})
10 changes: 7 additions & 3 deletions vhdmmio/core/register_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,8 @@ def __init__(self, cfg, trusted):

# Determine if the register file should be hardened against
# privilege escalation.
self._harden = (
any(map(lambda register: register.is_protected(), registers))
and not cfg.features.insecure)
self._need_prot = any(map(lambda register: register.is_protected(), registers))
self._harden = self._need_prot and not cfg.features.insecure

# Parse the interrupts.
self._interrupts = tuple((
Expand Down Expand Up @@ -145,6 +144,11 @@ def interrupt_info(self):
"""Information about the concatenated interrupt vector."""
return self._interrupt_info

@property
def need_prot(self):
"""Whether any field within the register file is prot-sensitive."""
return self._need_prot

@property
def harden(self):
"""Whether the register file should be hardened against privilege
Expand Down
20 changes: 13 additions & 7 deletions vhdmmio/vhdl/entity.template.vhd
Original file line number Diff line number Diff line change
Expand Up @@ -142,13 +142,13 @@ $endif
variable w_addr : std_logic_vector($ai.width-1$ downto 0);
variable w_data : std_logic_vector($bw-1$ downto 0) := (others => '0');
variable w_strb : std_logic_vector($bw-1$ downto 0) := (others => '0');
$if r.harden
$if r.need_prot
variable w_prot : std_logic_vector(2 downto 0) := (others => '0'); -- reg
$else
constant w_prot : std_logic_vector(2 downto 0) := (others => '0');
$endif
variable r_addr : std_logic_vector($ai.width-1$ downto 0);
$if r.harden
$if r.need_prot
variable r_prot : std_logic_vector(2 downto 0) := (others => '0'); -- reg
$else
constant r_prot : std_logic_vector(2 downto 0) := (others => '0');
Expand Down Expand Up @@ -493,7 +493,10 @@ $if r.harden
(awl.prot(0) = '0' and w_prot(0) = '1') or (awl.prot(1) = '1' and w_prot(1) = '0')
) then
if w_req then
w_nack := true;
bus_v.b.valid := '1';
bus_v.b.resp := AXI4L_RESP_SLVERR;
awl.valid := '0';
wl.valid := '0';
end if;
w_lreq := false;
w_req := false;
Expand All @@ -502,15 +505,18 @@ $if r.harden
(arl.prot(0) = '0' and r_prot(0) = '1') or (arl.prot(1) = '1' and r_prot(1) = '0')
) then
if r_req then
r_nack := true;
bus_v.r.valid := '1';
bus_v.r.resp := AXI4L_RESP_SLVERR;
bus_v.r.data := (others => '0');
arl.valid := '0';
end if;
r_lreq := false;
r_req := false;
end if;
$endif

-- Capture request inputs into more consistently named variables.
$if r.harden
$if r.need_prot
if w_req then
w_prot := awl.prot;
end if;
Expand All @@ -527,7 +533,7 @@ $endif
w_strb(b) := wl.strb(b / 8);
end loop;
w_data := wl.data and w_strb;
$if r.harden
$if r.need_prot
if r_req then
r_prot := arl.prot;
end if;
Expand Down Expand Up @@ -753,7 +759,7 @@ $if di.read_count
$endif
w_hstb := (others => '0');
w_hold := (others => '0');
$if r.harden
$if r.need_prot
w_prot := (others => '0');
r_prot := (others => '0');
$endif
Expand Down

0 comments on commit b8cf2f9

Please sign in to comment.