blob: bf4fb841852e585feb0762e8d086d2a0786e6c79 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tests.firmware;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import com.android.tradefed.util.AbiFormatter;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.TargetFileUtils;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.zip.GZIPInputStream;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
/* VTS test to verify boot/recovery image header. */
@RunWith(DeviceJUnit4ClassRunner.class)
public class FirmwareBootHeaderVerification extends BaseHostJUnit4Test {
private static final int PLATFORM_API_LEVEL_P = 28;
// Path to platform block devices.
private static final String BLOCK_DEV_PATH = "/dev/block/platform";
// Indicates current slot suffix for A/B devices.
private static final String PROPERTY_SLOT_SUFFIX = "ro.boot.slot_suffix";
private ITestDevice mDevice;
private String mBlockDevPath = BLOCK_DEV_PATH;
private int mLaunchApiLevel;
private String mSlotSuffix = null;
private static File mTemptFolder = null;
private ArrayList<String> mSupportedAbis = null;
@BeforeClass
public static void oneTimeSetup() throws Exception {
mTemptFolder = FileUtil.createTempDir("firmware-boot-header-verify");
}
@Before
public void setUp() throws Exception {
mDevice = getDevice();
mLaunchApiLevel = mDevice.getLaunchApiLevel();
mSlotSuffix = mDevice.getProperty(PROPERTY_SLOT_SUFFIX);
if (mSlotSuffix == null) {
mSlotSuffix = "";
}
CLog.i("Current slot suffix: %s", mSlotSuffix);
mSupportedAbis = new ArrayList<>(Arrays.asList(AbiFormatter.getSupportedAbis(mDevice, "")));
Assume.assumeTrue("Skipping test for x86 NON-ACPI ABI", isFullfeelPrecondition());
}
private boolean isFullfeelPrecondition() throws DeviceNotAvailableException {
if (mSupportedAbis.contains("x86")) {
mBlockDevPath = "/dev/block";
CommandResult cmdResult = mDevice.executeShellV2Command("cat /proc/cmdline |"
+ "grep -o \"'androidboot.acpio_idx=[^ ]*'\" |"
+ "cut -d \"=\" -f 2 ");
Assert.assertEquals(String.format("Checking if x86 device is NON-ACPI ABI: %s",
cmdResult.getStderr()),
cmdResult.getExitCode().intValue(), 0);
String acpio_idx_string = cmdResult.getStdout().replace("\n", "");
if (acpio_idx_string.equals("")) {
return false;
}
}
return true;
}
private void checkValidRamdisk(BootImageInfo bootImgInfo) throws IOException {
byte[] compressedRamdisk = bootImgInfo.getRamdiskStream();
ByteArrayInputStream compressedStream = new ByteArrayInputStream(compressedRamdisk);
GZIPInputStream extractRamdisk = new GZIPInputStream(compressedStream);
// The CPIO header magic can be "070701" or "070702" as per kernel
// documentation: Documentation/early-userspace/buffer-format.txt
byte[] cpio_header = new byte[6];
extractRamdisk.read(cpio_header);
CLog.i("cpio_header_magic: %s", cpio_header);
String cpio_header_magic = new String(cpio_header);
CLog.i("cpio_header_magic: %s", cpio_header_magic);
Assert.assertTrue("cpio archive header magic not found in ramdisk",
(cpio_header_magic.equals("070701") || cpio_header_magic.equals("070702")));
}
private void CheckImageHeader(String imagePath, boolean isRecovery) throws IOException {
BootImageInfo bootImgInfo = new BootImageInfo(imagePath);
// Check kernel size.
Assert.assertNotEquals(
"boot.img/recovery.img must contain kernel", bootImgInfo.getKernelSize(), 0);
if (mLaunchApiLevel > PLATFORM_API_LEVEL_P) {
// Check image version.
Assert.assertTrue("Device must at least have a boot image of version 2",
(bootImgInfo.getImgHeaderVer() >= 2));
// Check ramdisk size.
Assert.assertNotEquals(
"boot.img must contain ramdisk", bootImgInfo.getRamdiskSize(), 0);
checkValidRamdisk(bootImgInfo);
} else {
Assert.assertTrue("Device must at least have a boot image of version 1",
(bootImgInfo.getImgHeaderVer() >= 1));
}
if (isRecovery) {
Assert.assertNotEquals(
"recovery partition for non-A/B devices must contain the recovery DTBO",
bootImgInfo.getRecoveryDtboSize(), 0);
}
if (bootImgInfo.getImgHeaderVer() > 1) {
Assert.assertNotEquals(
"Boot/recovery image must contain DTB", bootImgInfo.getDtbSize(), 0);
}
Assert.assertEquals(
String.format(
"Test failure due to boot header size mismatch. Expected %s Actual %s",
bootImgInfo.getExpectHeaderSize(), bootImgInfo.getBootHeaderSize()),
bootImgInfo.getExpectHeaderSize(), bootImgInfo.getBootHeaderSize());
}
@AfterClass
public static void postTestTearDown() {
mTemptFolder.delete();
}
/* Validates boot image header. */
@Test
public void testBootImageHeader() throws Exception {
String currentBootPartition = "boot" + mSlotSuffix;
String[] options = {"-type", "l"};
ArrayList<String> bootPaths = TargetFileUtils.findFile(
mBlockDevPath, currentBootPartition, Arrays.asList(options), mDevice);
CLog.d("Boot path %s", bootPaths);
Assert.assertFalse("Unable to find path to boot image on device.", bootPaths.isEmpty());
File localBootImg = new File(mTemptFolder, "boot.img");
Assert.assertTrue("Pull " + bootPaths.get(0) + " failed!",
mDevice.pullFile(bootPaths.get(0), localBootImg));
CLog.d("Remote boot path %s", bootPaths);
CLog.d("Local boot path %s", localBootImg.getAbsolutePath());
CheckImageHeader(localBootImg.getAbsolutePath(), false);
}
/* Validates recovery image header. */
@Test
public void testRecoveryImageHeader() throws Exception {
Assume.assumeTrue("Skipping test: A/B devices do not have a separate recovery partition",
mSlotSuffix.equals(""));
String[] options = {"-type", "l"};
ArrayList<String> recoveryPaths = TargetFileUtils.findFile(
mBlockDevPath, "recovery", Arrays.asList(options), mDevice);
CLog.d("Recovery path %s", recoveryPaths);
Assert.assertFalse(
"Unable to find path to recovery image on device.", recoveryPaths.isEmpty());
File localRecoveryImg = new File(mTemptFolder, "recovery.img");
Assert.assertTrue("Pull " + recoveryPaths.get(0) + " failed!",
mDevice.pullFile(recoveryPaths.get(0), localRecoveryImg));
CLog.d("Remote boot path %s", recoveryPaths);
CLog.d("Local boot path %s", localRecoveryImg.getAbsolutePath());
CheckImageHeader(localRecoveryImg.getAbsolutePath(), true);
}
}