blob: 0cc84d010596d8f247edbfdecb103d7b22330168 [file] [log] [blame]
#!/bin/sh
# Copyright 2020 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# Utility for Chrome OS firmware write protection.
VERBOSE=
DEBUG=
NUMERIC=
TEMP_FILE=
# Cache of WP_RO section data
WP_RO_START=
WP_RO_LEN=
info() {
if [ -n "${VERBOSE}" ]; then
echo "INFO: $*" >&2
fi
}
debug() {
if [ -n "${DEBUG}" ]; then
echo "DEBUG: $*" >&2
fi
}
error() {
echo "ERROR: $*" >&2
}
die() {
error "$*"
exit 1
}
cleanup() {
if [ -n "${TEMP_FILE}" ]; then
rm -f "${TEMP_FILE}"
fi
}
on_abort() {
trap - EXIT
cleanup
die "Abort: failed to continue."
}
has_ec() {
[ -e /dev/cros_ec ]
}
get_temp_file() {
if [ -z "${TEMP_FILE}" ]; then
TEMP_FILE="$(mktemp cros_wp.XXXXXXXXXX --tmpdir)"
fi
}
check_release_normal() {
if ! crossystem cros_debug?0; then
info "Running on a debug image - ignore and exit."
return 1
fi
if ! crossystem mainfw_type?normal; then
info "Firmware not in normal mode - ignore and exit."
return 1
fi
get_temp_file
flashrom -p host -r "${TEMP_FILE}" -i FMAP -i GBB
futility gbb "${TEMP_FILE}" --rootkey="${TEMP_FILE}.rk"
mv -f "${TEMP_FILE}.rk" "${TEMP_FILE}"
local data="$(futility vbutil_key --unpack "${TEMP_FILE}" | \
grep 'Key sha1sum')"
local key_hash="${data##* }"
if [ -z "${key_hash}" ]; then
error "Failed to parse system firmware rootkey."
return 2
fi
if [ "${key_hash}" = "b11d74edd286c144e1135b49e7f0bc20cf041f10" ]; then
info "Current system is using DEV key firmware - ignore and exit."
return 1
fi
return 0
}
check_hardware_write_protect() {
debug "checking hardware write protection: wpsw_cur=$(crossystem wpsw_cur)"
crossystem wpsw_cur?1
}
get_fmap_wp_ro() {
local target="$1"
# Create temp file for accessing FMAP from firmware on flash.
get_temp_file
debug "Reading ${target} firmware FMAP for WP_RO data."
flashrom -p "${target}" -r "${TEMP_FILE}" -i FMAP >/dev/null 2>&1
# Data format: WP_RO start len (decimal)
local wp_data="$(dump_fmap -p "${TEMP_FILE}" WP_RO | sed 's/WP_RO //')"
debug "WP_RO from ${target} FMAP: ${wp_data}"
WP_RO_START="${wp_data% *}"
WP_RO_LEN="${wp_data#* }"
if [ -z "${WP_RO_START}" ] || [ -z "${WP_RO_LEN}" ]; then
error "Failed to parse WP range from ${target} firmware."
return 2
fi
return 0
}
check_software_write_protect() {
local target="$1"
debug "checking software write protection for ${target}"
local wp_output="$(flashrom -p "${target}" --wp-status 2>/dev/null)"
# Output format: WP: write protect range: start=0x00000000, len=0x00000000
local wp_start="$(echo "${wp_output}" | \
sed -n 's/WP.*start=\(0x[a-f0-9]\+\).*/\1/p')"
local wp_len="$(echo "${wp_output}" | \
sed -n 's/WP.*, len=\(0x[a-f0-9]\+\).*/\1/p')"
local wp_enabled="$(echo "${wp_output}" | grep 'is enabled.')"
debug "flashrom wp-status for ${target}: ${wp_output}"
if [ -z "${wp_start}" ] || [ -z "${wp_len}" ]; then
error "Failed to parse ${target} WP range from software WP settings."
return 2
fi
if [ -z "${wp_enabled}" ]; then
info "SW WP is not enabled."
return 1
fi
# Convert to decimal for comparing with WP_RO_START/LEN.
wp_start=$((wp_start))
wp_len=$((wp_len))
get_fmap_wp_ro "${target}"
if ! [ "${WP_RO_START}" -eq "${wp_start}" ]; then
error "Invalid WP start (expected: ${WP_RO_START}, found: ${wp_start})"
return 2
fi
if ! [ "${WP_RO_LEN}" -eq "${wp_len}" ]; then
error "Invalid WP length (expected: ${WP_RO_LEN}, found: ${wp_len})"
return 2
fi
info "WP range looks good (start=${WP_RO_START}, len=${wp_len})"
return 0
}
enable_software_write_protect() {
local target="$1"
echo "Enable software write protection for ${target}"
get_fmap_wp_ro "${target}"
flashrom -p "${target}" --wp-range ${WP_RO_START},${WP_RO_LEN} --wp-enable
}
enable_hardware_write_protect() {
if check_hardware_write_protect; then
info "HW WP already enabled."
return 0
fi
# Request to set WP mode
gsctool -a -w enable
# Check and ensure CCD status (CR50 factory mode).
# Reference: factory/py/gooftool/gsctool.py
if gsctool -a -I 2>/dev/null | grep -q 'Capabilities are modified'; then
echo "CR50 in factory mode - request to exit."
gsctool -a -F disable
if ! gsctool -a -I 2>/dev/null | grep -q 'Capabilities are default'; then
error "Failed to disable CCD and CR50 factory mode."
return 1
fi
fi
# TODO(hungte): This won't work if user 'reboots' device instead of 'shutdown'
# and we have to fix this after http://crbug.com/1121907 is implemented.
echo "HW WP requested - please start a cold reboot."
# Schedule an EC reboot
ectool reboot_ec cold at-shutdown
}
enable_write_protect() {
local target="$1"
# If host SW WP was already programmed, it will take effect immediately
# when HW WP changed from off to on. As a result, unless if you can be
# sure SW WP is not set (for example in factory), we should always
# configure SW WP then HW WP.
case "${target}" in
hw)
enable_hardware_write_protect
;;
host)
enable_software_write_protect host
enable_hardware_write_protect
;;
ec)
enable_software_write_protect ec
enable_hardware_write_protect
;;
""|all)
enable_software_write_protect host
if has_ec; then
enable_software_write_protect ec
fi
enable_hardware_write_protect
;;
*)
die "Unknown target: ${target}"
;;
esac
echo "Write protection settings updated. Please start a cold reboot."
}
report_write_protect() {
local target="$1"
local ret="0"
case "${target}" in
hw)
check_hardware_write_protect || ret="$?"
;;
host)
check_software_write_protect "${target}" || ret="$?"
;;
ec)
check_software_write_protect "${target}" || ret="$?"
;;
"")
check_hardware_write_protect || ret="$?"
if [ "${ret}" = 0 ]; then
check_software_write_protect host || ret="$?"
fi
if [ "${ret}" = 0 ] && has_ec; then
check_software_write_protect ec || ret="$?"
fi
;;
all)
echo "hw: $(report_write_protect hw || true)"
echo "host: $(report_write_protect host || true)"
if has_ec; then
echo "ec: $(report_write_protect ec || true)"
fi
return 0
;;
*)
die "Unknown target: ${target}"
;;
esac
local msg_on="on"
local msg_off="off"
local msg_error="error"
if [ -n "${NUMERIC}" ]; then
msg_on="1"
msg_off="0"
msg_error="-2"
fi
case "${ret}" in
0)
echo "${msg_on}"
;;
1)
echo "${msg_off}"
;;
*)
echo "${msg_error}"
esac
}
usage() {
echo "Usage: cros_wp [options] [command]
Set or get write protection status.
The 'write protection' on Chrome OS is controlled by multiple settings
(hardware, software <status and range>) for different targets (host=AP,
EC, PD, ...). And the firmware is only 'write protected' if all settings
are properly configured.
To get system overall status: cros_wp
To get a list of individual settings: cros_wp -t all
To turn on ALL write protection: cros_wp enable
Options:
-t: Specify target (hw, host, ec, all)
-n: Report status using numeric values.
-v: Provide verbose messages
-d: Print out debug messages
-h: Print this usage (help) information
Command:
enable: Turn on write protection
status: Prints 'on', 'off', or 'error' (1, 0, -2 in numeric mode)
enable_for_dogfood: Turn on write protection only for dogfood units
running non-dev signed firmware in release image normal mode
If no commands specified the default command is 'status'.
">&2
}
main() {
local target=""
eval set -- $(getopt -n "$0" t:nvdh "$@")
if [ $? -ne 0 ]; then
usage
die "Failed parsing arguments."
fi
while true; do
case "$1" in
-t)
target="$2"
shift 2
;;
-n)
NUMERIC=1
shift
;;
-v)
VERBOSE=1
shift
;;
-d)
DEBUG=1
shift
;;
-h)
usage
exit
;;
--)
shift
break
;;
*)
die "Unexpected argument: $1"
;;
esac
done
local command="$1"
debug "TARGET: ${target}, COMMAND: ${command}"
set -e
trap on_abort EXIT
case "${command}" in
status|"")
report_write_protect "${target}"
;;
enable)
enable_write_protect "${target}"
;;
enable_for_dogfood)
VERBOSE=1 # This is usually done inside AU and we want full logs.
if check_release_normal; then
enable_write_protect "${target}"
fi
;;
*)
die "Unknown command: ${command}"
;;
esac
trap - EXIT
cleanup
}
main "$@"