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:

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 generated

  • max_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 pattern

  • distance_one - indicates whether the attack has a distance one component

  • double_sided - is the attack double-sided?

  • distance_two - indicates whether the attack has a distance two component

  • attack_rows_start - the index of the first attack row

  • max_attack_row_idx - the position of the last attack row relative to attack_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 used

  • row_generator - the row generator class used to generate the rows

  • row_generator_config - parameters for the row generator

  • verbose - should verbose output be generated (true or false)

  • fill_local - when enabled, permits shrinking the filled memory area to aggressor and victim rows

  • read_count - number of hammers (reads) per row

  • refresh - 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 used

  • row_generator - this is the row generator class used to generate the rows

  • row_generator_config - parameters for the row generator

  • verbose - should verbose output be generated (true or false)

  • fill_local - when enabled, permits shrinking the filled memory area to just the aggressors and the victim

  • nr_rows - number of rows to conduct the experiment over. This is the number of aggressor rows

    • The 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 row

    • This 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 to read_count_step if unspecified

  • distance - distance between aggressors and victim. Defaults to 1

  • baseline - when enabled, a retention effect baseline is collected by hammering distant rows for the same amount of time as the aggressor rows

  • first_dummy_row - location of the first of two dummy rows used for baselining

  • iters_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:

  1. Dilution is increased until pure distance-one attacks stop working.

  2. Verify that pure distance-two attack doesn’t work.

  3. 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.

  4. 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 iteration

  • read_count_steps - the amount to decrement the number of hammers for each iteration of the outer loop

  • initial_dilution - initial value for dilution. Dilution resets to this value at the beginning of the inner loop

  • dilution_multiplier - dilution is multiplied by this value for each iteration of the inner loop

  • verbose - generates more output

  • row_mapping - specifies the style in which the rows are mapped on the chip

  • attack_rows_start - starting row number for rows used to attack the victim

  • max_attack_row_idx - index measured from attack_rows_start for the last attack row

  • decoy_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 comparable

  • max_dilution - maximum value for dilution

  • fill_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 use

  • row_pattern - pattern that will be stored in rows

  • inversion_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.

Table 2 Inversion example table

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


Last update: 2024-12-18