Test-writing playbook¶
The Playbook directory contains a group of Python classes and scripts designed to simplify the process of writing various rowhammer-related tests. These tests can be executed against a hardware platform.
Payload¶
Tests are generated as payload
data.
After generation, this data is transferred to a memory area in the device reserved for this purpose called payload memory
.
The payload contains an instruction list that can be interpreted by the payload executor
module on hardware.
The payload executor translates these instructions into DRAM commands.
The payload executor connects directly to the DRAM PHY, bypassing the DRAM controller, as explained in the Architecture section.
Changing payload memory size¶
Payload memory size can be changed up to the limit of memory available on the hardware platform used. The payload memory size is defined in common.py, as an argument to LiteX:
add_argument("--payload-size", default="1024", help="Payload memory size in bytes")
The examples shown in this chapter don’t require any changes. When writing your own configurations, you may need to change the default value.
Row mapping¶
In the case of DRAM modules, the physical layout of the memory rows in hardware can be different from the logical numbers assigned to them.
The nature of a rowhammer attack is such that only the physically adjacent rows are affected by the aggressor row
.
There are several mapping strategies that can be implemented to deal with the problem of disparity between physical location and logical enumeration:
TrivialRowMapping
- logical address is the same as physical addressTypeARowMapping
- a more complex mapping method, reverse-engineered as a part of the Defeating Software Mitigations against Rowhammer: a Surgical Precision Hammer paperTypeBRowMapping
- logical address is the physical one multiplied by 2, taken from Revisiting RowHammer: An Experimental Analysis of Modern DRAM Devices and Mitigation Techniques
Row Generator class¶
The Row Generator class is responsible for creating a list of row numbers used in a rowhammer attack. Currently, one instance of this class is available.
EvenRowGenerator¶
Generates a list of even numbered rows. Uses the row mapping specified by the payload generator class used. Two configuration parameters are needed for EvenRowGenerator:
nr_rows
- number of rows to be generatedmax_row
- maximal number to be used. The chosen numbers will be modulo max_row
HalfDoubleRowGenerator¶
Generates a list of rows for a Half-Double attack.
The list will repeat rows to create weight difference between attacks at different distances.
Used by HalfDoubleAnalysisPayloadGenerator
.
nr_rows
- number of rows in the attack patterndistance_one
- indicates whether the attack has a distance one componentdouble_sided
- is the attack double-sided?distance_two
- indicates whether the attack has a distance two componentattack_rows_start
- the index of the first attack rowmax_attack_row_idx
- the position of the last attack row relative toattack_rows_start
decoy_rows_start
- the index of the first decoy row; there are 3 decoy rows that are used as placebos in various situations: to hammer away from the victim but to keep the number of hammers and timing constant
Payload generator class¶
The purpose of the payload generator is to prepare a payload and process the test outcome. It is a class that can be reused in different tests configurations. Payload generators are located in the payload_generators directory
Available payload generators¶
Row mapping and row generator settings are combined into a payload generator class. Currently, two payload generators are available.
RowListPayloadGenerator¶
RowListPayloadGenerator is a simple payload generator that can use a RowGenerator class to generate rows, and then generate a payload that hammers the rows in this list.
(hammering
is a term used to describe multiple read operations on the same row).
RowListPayloadGenerator can also issue refresh commands to the DRAM module.
The configs that can be used in payload_generator_config for this payload generator are listed below:
row_mapping
- the row mapping usedrow_generator
- the row generator class used to generate the rowsrow_generator_config
- parameters for the row generatorverbose
- should verbose output be generated (true or false)fill_local
- when enabled, permits shrinking the filled memory area to aggressor and victim rowsread_count
- number of hammers (reads) per rowrefresh
- should refresh be enabled (true or false)
Examples of configurations for this test are provided as configs/example_row_list_*.json
files.
Some of them require a significant amount of memory declared as payload memory
.
To execute a minimalistic example from within the rowhammer-tester repo, execute:
source venv/bin/activate
export TARGET=arty # change accordingly
cd rowhammer_tester/scripts/playbook/
python playbook.py configs/example_row_list_minimal.json
Expected output:
Progress: [========================================] 65536 / 65536
Row sequence:
[0, 2, 4, 6, 14, 12, 10, 8, 16, 18]
Generating payload:
tRAS = 5
tRP = 3
tREFI = 782
tRFC = 32
Repeatable unit: 930
Repetitions: 93
Payload size = 0.10KB / 1.00KB
Payload per-row toggle count = 0.010K x10 rows
Payload refreshes (if enabled) = 10 (disabled)
Expected execution time = 1903 cycles = 0.019 ms
Transferring the payload ...
Executing ...
Time taken: 0.738 ms
Progress: [== ] 3338 / 65536 (Errors: 1287)
...
HammerTolerancePayloadGenerator¶
HammerTolerancePayloadGenerator is a payload generator for measuring and characterizing rowhammer tolerance. It can provide information about how many rows and bits are susceptible to the rowhammer attack. It can also provide information about the location of susceptible bits.
A series of double-sided hammers against the available group of victim rows is performed.
The double-sided hammers increase in intensity based on read_count_step
parameter.
Here are the parameters that can be specified in payload_generator_config for this payload generator:
row_mapping
- this is the row mapping usedrow_generator
- this is the row generator class used to generate the rowsrow_generator_config
- parameters for the row generatorverbose
- should verbose output be generated (true or false)fill_local
- when enabled, permits shrinking the filled memory area to just the aggressors and the victimnr_rows
- number of rows to conduct the experiment over. This is the number of aggressor rowsThe number of victim rows will be lower by 2; for example, to perform hammering for 32 victim rows, use 34 as the parameter value
read_count_step
- this is how much to increment the hammer count between multiple tests for the same rowThis is the number of hammers on a single side (total number of hammers on both sides is 2x this value)
initial_read_count
- hammer count for the first test for a given row. Defaults toread_count_step
if unspecifieddistance
- distance between aggressors and victim. Defaults to 1baseline
- when enabled, a retention effect baseline is collected by hammering distant rows for the same amount of time as the aggressor rowsfirst_dummy_row
- location of the first of two dummy rows used for baseliningiters_per_row
- number of times the hammer count is incremented for each row
The results are a series of histograms with appropriate labeling.
Example configurations for this test ar provided as configs/example_hammer_*.json
files.
Some of them require a significant amount of memory declared as payload memory
.
To execute a minimalistic example from within the rowhammer-tester repo, execute:
source venv/bin/activate
export TARGET=arty # change accordingly
cd rowhammer_tester/scripts/playbook/
python playbook.py configs/example_hammer_minimal.json
Expected output:
Progress: [========================================] 3072 / 3072
Generating payload:
tRAS = 5
tRP = 3
tREFI = 782
tRFC = 32
Repeatable unit: 186
Repetitions: 93
Payload size = 0.04KB / 1.00KB
Payload per-row toggle count = 0.010K x2 rows
Payload refreshes (if enabled) = 10 (disabled)
Expected execution time = 1263 cycles = 0.013 ms
Transferring the payload ...
Executing ...
Time taken: 0.647 ms
Progress: [============ ] 323 / 1024 (Errors: 320)
...
HalfDoubleAnalysisPayloadGenerator¶
Half-Double is a Rowhammer phenomenon where accesses to both distance-one and distance-two neighbors of a victim row are used to generate bit flips. This payload generator allows us to characterize the Half-Double effect on a memory part.
For each candidate victim row, the analysis starts out with the maximum number of hammers and minimum dilution level. Then, we proceed as follows:
Dilution is increased until pure distance-one attacks stop working.
Verify that pure distance-two attack doesn’t work.
Increase dilution level and record the number of bit flips in the victim until either the bit flips stop or maximum dilution level is reached.
Once maximum dilution level is reached or bit flips stop, reduce hammer count by a step and reset dilution to initial level and retry step 3. Repeat until the lowest hammer count is reached.
Note
The hammer count changes on a linear scale and dilution changes on an exponential scale.
Results are presented as a table of values with columns representing the hammer count and the rows representing dilution levels. See Tables 2 and 3 in the Half-Double white paper as examples.
max_total_read_count
- maximum number of hammers issued to any given row during an iterationread_count_steps
- the amount to decrement the number of hammers for each iteration of the outer loopinitial_dilution
- initial value for dilution. Dilution resets to this value at the beginning of the inner loopdilution_multiplier
- dilution is multiplied by this value for each iteration of the inner loopverbose
- generates more outputrow_mapping
- specifies the style in which the rows are mapped on the chipattack_rows_start
- starting row number for rows used to attack the victimmax_attack_row_idx
- index measured fromattack_rows_start
for the last attack rowdecoy_rows_start
- the position of the first decoy row. There are three decoy rows. They are used as placebos during pure distance-one portions of the experiment to make the number of hammers and their timing comparablemax_dilution
- maximum value for dilutionfill_local
- only reinitialize affected rows between experiments, as an optimization
Configurations¶
Test configuration files are represented as JSON files. An example:
{
"inversion_divisor" : 2,
"inversion_mask" : "0b10",
"payload_generator" : "RowListPayloadGenerator",
"payload_generator_config" : {
"row_mapping" : "TypeARowMapping",
"row_generator" : "EvenRowGenerator",
"read_count" : 27,
"max_iteration" : 10,
"verbose" : true,
"refresh" : false,
"fill_local" : true,
"row_generator_config" : {
"nr_rows" : 10,
"max_row" : 64
}
}
}
The following parameters are supported:
payload_generator
- name of the payload generator class to userow_pattern
- pattern that will be stored in rowsinversion_divisor and inversion_mask - controls which rows get the inverted pattern described in the inversion section
payload_generator_config
- these parameters are specific for the payload generator class used
Inversion¶
If needed, use the bitwise-inverted
data pattern for selected tested rows.
Two parameters are used to specify which rows are to be inverted:
inversion_divisor
inversion_mask
Example:
inversion_divisor = 8
, inversion_mask = 0b10010010
(bits 1, 4 and 7 are “on”).
We iterate through all row numbers 0,1,2,3,4,…,8,9,10,…
First, a modulo inversion_divisor
operation is performed on a row number.
In this case, it’s mod 8
.
Next, we check if the bit in inversion_mask
in the position corresponding to our row number (after modulo) is “on” or “off”.
If it’s “on”, this whole row will be inverted.
The results for our example are visible in Table 2 below.
Row number |
Row number modulo divisor (8) |
Value |
---|---|---|
0 |
0 |
pattern |
1 |
1 |
inverted pattern |
2 |
2 |
pattern |
3 |
3 |
pattern |
4 |
4 |
inverted pattern |
5 |
5 |
pattern |
6 |
6 |
pattern |
7 |
7 |
inverted pattern |
8 |
0 |
pattern |
9 |
1 |
inverted pattern |
10 |
2 |
pattern |
11 |
3 |
pattern |
12 |
4 |
inverted pattern |