mirror of
https://review.coreboot.org/flashrom.git
synced 2025-10-13 22:10:23 +02:00
Fail immediately when trying to write/erase wp regions
This patch introduces chipset-level protections and adds checks that abort writing to flash if any of the requested regions are write-protected by chip, dynamically by a chipset, or are defined as read-only. This change is done so it's harder for user to brick his own platform. Information about read-only regions can easily be missed as flashrom can output a lot of information on screen. Even if you notice you might not know if one of the regions you requested falls inside read-only range, especially if using different names for those regions. If you are flashing multiple regions or ones that partially overlap with read-only parts then that could result in flashrom failing in the middle leaving you in unknown state. This patch was tested with multiple combinations of unprotected/protected regions: - dummy programmer ```sh flashrom -p dummy:hwwp=yes,emulate=S25FL128L --wp-enable \ --wp-range 0x00040000,0x00fc0000 \ -l <(echo '00000000:0004ffff part1') -i part1 -E ``` - internal programmer on Protectli VP6670 with Dasharo UEFI firmware with locked BIOS boot medium (PR0, part of bios region) ```sh flashrom -p internal --ifd -i me -i bios -w test.rom ``` Normal reads and flashing non-protected regions was also tested. Change-Id: Ia0dd847923e20ff0081ceae68984369e98952c2f Signed-off-by: Michał Iwanicki <michal.iwanicki@3mdeb.com> Reviewed-on: https://review.coreboot.org/c/flashrom/+/89222 Reviewed-by: Sergii Dmytruk <sergii.dmytruk@3mdeb.com> Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Anastasia Klimchuk <aklm@chromium.org>
This commit is contained in:

committed by
Anastasia Klimchuk

parent
45fc49e99b
commit
2a092bbef7
@@ -23,3 +23,18 @@ Added support
|
|||||||
* Intel Wildcat Lake chipset
|
* Intel Wildcat Lake chipset
|
||||||
* Eon EN25QX128A
|
* Eon EN25QX128A
|
||||||
* PUYA P25D80H
|
* PUYA P25D80H
|
||||||
|
|
||||||
|
New features
|
||||||
|
============
|
||||||
|
|
||||||
|
Fail immediately when trying to write/erase wp regions
|
||||||
|
------------------------------------------------------
|
||||||
|
|
||||||
|
This change is done so it's harder for user to brick his own platform.
|
||||||
|
Information about read-only regions can easily be missed as flashrom
|
||||||
|
can output a lot of information on screen. Even if you notice you might
|
||||||
|
not know if one of the regions you requested falls inside read-only
|
||||||
|
range, especially if using different names for those regions.
|
||||||
|
If you are flashing multiple regions or ones that partially overlap with
|
||||||
|
read-only parts then that could result in flashrom failing in the
|
||||||
|
middle, leaving you in unknown state.
|
||||||
|
75
flashrom.c
75
flashrom.c
@@ -384,6 +384,22 @@ void get_flash_region(const struct flashctx *flash, int addr, struct flash_regio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void get_protected_ranges(const struct flashctx *flash, struct protected_ranges *ranges) {
|
||||||
|
if ((flash->mst->buses_supported & BUS_PROG) && flash->mst->opaque.get_protected_ranges) {
|
||||||
|
flash->mst->opaque.get_protected_ranges(ranges);
|
||||||
|
} else {
|
||||||
|
*ranges = (const struct protected_ranges){ 0 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void release_protected_ranges(const struct flashctx *flash, struct protected_ranges *ranges) {
|
||||||
|
for (int i = 0; i < ranges->count; ++i) {
|
||||||
|
free(ranges->ranges[i].name);
|
||||||
|
}
|
||||||
|
free(ranges->ranges);
|
||||||
|
*ranges = (const struct protected_ranges){ 0 };
|
||||||
|
}
|
||||||
|
|
||||||
int check_for_unwritable_regions(const struct flashctx *flash, unsigned int start, unsigned int len)
|
int check_for_unwritable_regions(const struct flashctx *flash, unsigned int start, unsigned int len)
|
||||||
{
|
{
|
||||||
struct flash_region region;
|
struct flash_region region;
|
||||||
@@ -1817,6 +1833,58 @@ warn_out:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool can_change_target_regions(struct flashctx *flash) {
|
||||||
|
bool check_wp = false;
|
||||||
|
size_t wp_start, wp_len;
|
||||||
|
enum flashrom_wp_mode mode;
|
||||||
|
struct flashrom_wp_cfg *cfg = NULL;
|
||||||
|
const struct romentry *entry = NULL;
|
||||||
|
const struct flashrom_layout *const layout = get_layout(flash);
|
||||||
|
struct protected_ranges ranges;
|
||||||
|
get_protected_ranges(flash, &ranges);
|
||||||
|
|
||||||
|
if (flashrom_wp_cfg_new(&cfg) == FLASHROM_WP_OK &&
|
||||||
|
flashrom_wp_read_cfg(cfg, flash) == FLASHROM_WP_OK)
|
||||||
|
{
|
||||||
|
flashrom_wp_get_range(&wp_start, &wp_len, cfg);
|
||||||
|
mode = flashrom_wp_get_mode(cfg);
|
||||||
|
if (mode != FLASHROM_WP_MODE_DISABLED && wp_len != 0)
|
||||||
|
check_wp = true;
|
||||||
|
}
|
||||||
|
flashrom_wp_cfg_release(cfg);
|
||||||
|
|
||||||
|
while ((entry = layout_next_included(layout, entry))) {
|
||||||
|
if (!flash->flags.skip_unwritable_regions &&
|
||||||
|
check_for_unwritable_regions(flash, entry->region.start,
|
||||||
|
entry->region.end - entry->region.start + 1))
|
||||||
|
{
|
||||||
|
release_protected_ranges(flash, &ranges);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (check_wp && entry->region.start < wp_start + wp_len && wp_start <= entry->region.end) {
|
||||||
|
msg_gerr("%s: cannot fully update %s region (%#08"PRIx32"..%#08"PRIx32")"
|
||||||
|
" due to chip's write-protection.\n", __func__,
|
||||||
|
entry->region.name, entry->region.start, entry->region.end);
|
||||||
|
release_protected_ranges(flash, &ranges);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < ranges.count; ++i) {
|
||||||
|
struct flash_region prot = ranges.ranges[i];
|
||||||
|
if (prot.write_prot && prot.start <= entry->region.end &&
|
||||||
|
prot.end >= entry->region.start) {
|
||||||
|
msg_gerr("%s: cannot fully update %s region (%#08"PRIx32"..%#08"PRIx32")"
|
||||||
|
" due to chipset write-protection.\n", __func__,
|
||||||
|
entry->region.name, entry->region.start, entry->region.end);
|
||||||
|
release_protected_ranges(flash, &ranges);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
release_protected_ranges(flash, &ranges);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
int prepare_flash_access(struct flashctx *const flash,
|
int prepare_flash_access(struct flashctx *const flash,
|
||||||
const bool read_it, const bool write_it,
|
const bool read_it, const bool write_it,
|
||||||
const bool erase_it, const bool verify_it)
|
const bool erase_it, const bool verify_it)
|
||||||
@@ -1831,6 +1899,13 @@ int prepare_flash_access(struct flashctx *const flash,
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((write_it || erase_it) && !flash->flags.force) {
|
||||||
|
if (!can_change_target_regions(flash)) {
|
||||||
|
msg_cerr("At least one target region is not fully writable. Aborting.\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (map_flash(flash) != 0)
|
if (map_flash(flash) != 0)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
|
59
ichspi.c
59
ichspi.c
@@ -1287,6 +1287,13 @@ struct hwseq_data {
|
|||||||
struct fd_region fd_regions[MAX_FD_REGIONS];
|
struct fd_region fd_regions[MAX_FD_REGIONS];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define MAX_PR_REGISTERS 6
|
||||||
|
struct flash_region ranges_data[MAX_PR_REGISTERS];
|
||||||
|
struct protected_ranges ranges = {
|
||||||
|
.count = 0,
|
||||||
|
.ranges = ranges_data,
|
||||||
|
};
|
||||||
|
|
||||||
static struct hwseq_data *get_hwseq_data_from_context(const struct flashctx *flash)
|
static struct hwseq_data *get_hwseq_data_from_context(const struct flashctx *flash)
|
||||||
{
|
{
|
||||||
return flash->mst->opaque.data;
|
return flash->mst->opaque.data;
|
||||||
@@ -1469,6 +1476,21 @@ static void ich_get_region(const struct flashctx *flash, unsigned int addr, stru
|
|||||||
region->name = strdup(name);
|
region->name = strdup(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void ich_get_protected_ranges(struct protected_ranges *protected_ranges) {
|
||||||
|
protected_ranges->count = ranges.count;
|
||||||
|
protected_ranges->ranges = malloc(sizeof(*ranges.ranges) * ranges.count);
|
||||||
|
for (int i = 0; i < ranges.count; ++i) {
|
||||||
|
if (ranges.ranges[i].name == NULL)
|
||||||
|
protected_ranges->ranges[i].name = strdup("");
|
||||||
|
else
|
||||||
|
protected_ranges->ranges[i].name = strdup(ranges.ranges[i].name);
|
||||||
|
protected_ranges->ranges[i].start = ranges.ranges[i].start;
|
||||||
|
protected_ranges->ranges[i].end = ranges.ranges[i].end;
|
||||||
|
protected_ranges->ranges[i].read_prot = ranges.ranges[i].read_prot;
|
||||||
|
protected_ranges->ranges[i].write_prot = ranges.ranges[i].write_prot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Given RDID info, return pointer to entry in flashchips[] */
|
/* Given RDID info, return pointer to entry in flashchips[] */
|
||||||
static const struct flashchip *flash_id_to_entry(uint32_t mfg_id, uint32_t model_id)
|
static const struct flashchip *flash_id_to_entry(uint32_t mfg_id, uint32_t model_id)
|
||||||
{
|
{
|
||||||
@@ -1952,13 +1974,19 @@ static enum ich_access_protection ich9_handle_region_access(struct fd_region *fd
|
|||||||
#define ICH_PR_PERMS(pr) (((~((pr) >> PR_RP_OFF) & 1) << 0) | \
|
#define ICH_PR_PERMS(pr) (((~((pr) >> PR_RP_OFF) & 1) << 0) | \
|
||||||
((~((pr) >> PR_WP_OFF) & 1) << 1))
|
((~((pr) >> PR_WP_OFF) & 1) << 1))
|
||||||
|
|
||||||
static enum ich_access_protection ich9_handle_pr(const size_t reg_pr0, unsigned int i)
|
static enum ich_access_protection ich9_handle_pr(const size_t reg_pr0, unsigned int i, struct flash_region *prot)
|
||||||
{
|
{
|
||||||
uint8_t off = reg_pr0 + (i * 4);
|
uint8_t off = reg_pr0 + (i * 4);
|
||||||
uint32_t pr = mmio_readl(ich_spibar + off);
|
uint32_t pr = mmio_readl(ich_spibar + off);
|
||||||
unsigned int rwperms_idx = ICH_PR_PERMS(pr);
|
unsigned int rwperms_idx = ICH_PR_PERMS(pr);
|
||||||
enum ich_access_protection rwperms = access_perms_to_protection[rwperms_idx];
|
enum ich_access_protection rwperms = access_perms_to_protection[rwperms_idx];
|
||||||
|
|
||||||
|
prot->name = NULL;
|
||||||
|
prot->start = ICH_FREG_BASE(pr);
|
||||||
|
prot->end = ICH_FREG_LIMIT(pr);
|
||||||
|
prot->write_prot = (rwperms == WRITE_PROT) || (rwperms == LOCKED);
|
||||||
|
prot->read_prot = (rwperms == READ_PROT) || (rwperms == LOCKED);
|
||||||
|
|
||||||
/* From 5 on we have GPR registers and start from 0 again. */
|
/* From 5 on we have GPR registers and start from 0 again. */
|
||||||
const char *const prefix = i >= 5 ? "G" : "";
|
const char *const prefix = i >= 5 ? "G" : "";
|
||||||
if (i >= 5)
|
if (i >= 5)
|
||||||
@@ -2012,16 +2040,17 @@ static const struct spi_master spi_master_ich = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static const struct opaque_master opaque_master_ich_hwseq = {
|
static const struct opaque_master opaque_master_ich_hwseq = {
|
||||||
.max_data_read = 64,
|
.max_data_read = 64,
|
||||||
.max_data_write = 64,
|
.max_data_write = 64,
|
||||||
.probe = ich_hwseq_probe,
|
.probe = ich_hwseq_probe,
|
||||||
.read = ich_hwseq_read,
|
.read = ich_hwseq_read,
|
||||||
.write = ich_hwseq_write,
|
.write = ich_hwseq_write,
|
||||||
.erase = ich_hwseq_block_erase,
|
.erase = ich_hwseq_block_erase,
|
||||||
.read_register = ich_hwseq_read_status,
|
.read_register = ich_hwseq_read_status,
|
||||||
.write_register = ich_hwseq_write_status,
|
.write_register = ich_hwseq_write_status,
|
||||||
.get_region = ich_get_region,
|
.get_region = ich_get_region,
|
||||||
.shutdown = ich_hwseq_shutdown,
|
.get_protected_ranges = ich_get_protected_ranges,
|
||||||
|
.shutdown = ich_hwseq_shutdown,
|
||||||
};
|
};
|
||||||
|
|
||||||
static int init_ich7_spi(void *spibar, enum ich_chipset ich_gen)
|
static int init_ich7_spi(void *spibar, enum ich_chipset ich_gen)
|
||||||
@@ -2248,7 +2277,13 @@ static int init_ich_default(const struct programmer_cfg *cfg, void *spibar, enum
|
|||||||
/* if not locked down try to disable PR locks first */
|
/* if not locked down try to disable PR locks first */
|
||||||
if (!ichspi_lock)
|
if (!ichspi_lock)
|
||||||
ich9_set_pr(reg_pr0, i, 0, 0);
|
ich9_set_pr(reg_pr0, i, 0, 0);
|
||||||
ich_spi_rw_restricted |= ich9_handle_pr(reg_pr0, i);
|
struct flash_region pr_region = { 0 };
|
||||||
|
enum ich_access_protection rwperms = ich9_handle_pr(reg_pr0, i, &pr_region);
|
||||||
|
ich_spi_rw_restricted |= rwperms;
|
||||||
|
if (rwperms != NO_PROT) {
|
||||||
|
ranges_data[ranges.count] = pr_region;
|
||||||
|
ranges.count += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (ich_spi_rw_restricted) {
|
switch (ich_spi_rw_restricted) {
|
||||||
|
@@ -57,6 +57,11 @@ struct romentry {
|
|||||||
struct flash_region region;
|
struct flash_region region;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct protected_ranges {
|
||||||
|
int count;
|
||||||
|
struct flash_region *ranges;
|
||||||
|
};
|
||||||
|
|
||||||
struct flashrom_layout;
|
struct flashrom_layout;
|
||||||
|
|
||||||
struct layout_include_args;
|
struct layout_include_args;
|
||||||
@@ -80,5 +85,11 @@ void prepare_layout_for_extraction(struct flashrom_flashctx *);
|
|||||||
int layout_sanity_checks(const struct flashrom_flashctx *);
|
int layout_sanity_checks(const struct flashrom_flashctx *);
|
||||||
int check_for_unwritable_regions(const struct flashrom_flashctx *flash, unsigned int start, unsigned int len);
|
int check_for_unwritable_regions(const struct flashrom_flashctx *flash, unsigned int start, unsigned int len);
|
||||||
void get_flash_region(const struct flashrom_flashctx *flash, int addr, struct flash_region *region);
|
void get_flash_region(const struct flashrom_flashctx *flash, int addr, struct flash_region *region);
|
||||||
|
/*
|
||||||
|
* Return chipset-level protections.
|
||||||
|
* ranges parameter has to be freed by the caller with release_protected_ranges
|
||||||
|
*/
|
||||||
|
void get_protected_ranges(const struct flashrom_flashctx *flash, struct protected_ranges *ranges);
|
||||||
|
void release_protected_ranges(const struct flashrom_flashctx *flash, struct protected_ranges *ranges);
|
||||||
|
|
||||||
#endif /* !__LAYOUT_H__ */
|
#endif /* !__LAYOUT_H__ */
|
||||||
|
@@ -397,6 +397,12 @@ struct opaque_master {
|
|||||||
enum flashrom_wp_result (*wp_read_cfg)(struct flashrom_wp_cfg *, struct flashctx *);
|
enum flashrom_wp_result (*wp_read_cfg)(struct flashrom_wp_cfg *, struct flashctx *);
|
||||||
enum flashrom_wp_result (*wp_get_ranges)(struct flashrom_wp_ranges **, struct flashctx *);
|
enum flashrom_wp_result (*wp_get_ranges)(struct flashrom_wp_ranges **, struct flashctx *);
|
||||||
void (*get_region)(const struct flashctx *flash, unsigned int addr, struct flash_region *region);
|
void (*get_region)(const struct flashctx *flash, unsigned int addr, struct flash_region *region);
|
||||||
|
/*
|
||||||
|
* Returns chipset-level protections (e.g., when SPI controller refuses
|
||||||
|
* to pass read/write commands to the flash chip based on chipset's
|
||||||
|
* configuration)
|
||||||
|
*/
|
||||||
|
void (*get_protected_ranges)(struct protected_ranges *);
|
||||||
int (*shutdown)(void *data);
|
int (*shutdown)(void *data);
|
||||||
void (*delay) (const struct flashctx *flash, unsigned int usecs);
|
void (*delay) (const struct flashctx *flash, unsigned int usecs);
|
||||||
void *data;
|
void *data;
|
||||||
|
Reference in New Issue
Block a user