205 lines
6.1 KiB
Python
205 lines
6.1 KiB
Python
# Copyright 2018 The gVisor Authors.
|
|
#
|
|
# 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.
|
|
|
|
"""Verify VDSO ELF does not contain any relocations and is directly mmappable.
|
|
"""
|
|
|
|
import argparse
|
|
import logging
|
|
import re
|
|
import subprocess
|
|
|
|
PAGE_SIZE = 4096
|
|
|
|
|
|
def PageRoundDown(addr):
|
|
"""Rounds down to the nearest page.
|
|
|
|
Args:
|
|
addr: An address.
|
|
|
|
Returns:
|
|
The address rounded down to thie nearest page.
|
|
"""
|
|
return addr & ~(PAGE_SIZE - 1)
|
|
|
|
|
|
def Fatal(*args, **kwargs):
|
|
"""Logs a critical message and exits with code 1.
|
|
|
|
Args:
|
|
*args: Args to pass to logging.critical.
|
|
**kwargs: Keyword args to pass to logging.critical.
|
|
"""
|
|
logging.critical(*args, **kwargs)
|
|
exit(1)
|
|
|
|
|
|
def CheckSegments(vdso_path):
|
|
"""Verifies layout of PT_LOAD segments.
|
|
|
|
PT_LOAD segments must be laid out such that the ELF is directly mmappable.
|
|
|
|
Specifically, check that:
|
|
* PT_LOAD file offsets are equivalent to the memory offset from the first
|
|
segment.
|
|
* No extra zeroed space (memsz) is required.
|
|
* PT_LOAD segments are in order (required for any ELF).
|
|
* No two PT_LOAD segments share part of the same page.
|
|
|
|
The readelf line format looks like:
|
|
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
|
|
LOAD 0x000000 0xffffffffff700000 0xffffffffff700000 0x000e68 0x000e68 R E 0x1000
|
|
|
|
Args:
|
|
vdso_path: Path to VDSO binary.
|
|
"""
|
|
output = subprocess.check_output(["readelf", "-lW", vdso_path]).decode()
|
|
lines = output.split("\n")
|
|
|
|
segments = []
|
|
for line in lines:
|
|
if not line.startswith(" LOAD"):
|
|
continue
|
|
|
|
components = line.split()
|
|
|
|
segments.append({
|
|
"offset": int(components[1], 16),
|
|
"addr": int(components[2], 16),
|
|
"filesz": int(components[4], 16),
|
|
"memsz": int(components[5], 16),
|
|
})
|
|
|
|
if not segments:
|
|
Fatal("No PT_LOAD segments in VDSO")
|
|
|
|
first = segments[0]
|
|
if first["offset"] != 0:
|
|
Fatal("First PT_LOAD segment has non-zero file offset: %s", first)
|
|
|
|
for i, segment in enumerate(segments):
|
|
memoff = segment["addr"] - first["addr"]
|
|
if memoff != segment["offset"]:
|
|
Fatal("PT_LOAD segment has different memory and file offsets: %s",
|
|
segments)
|
|
|
|
if segment["memsz"] != segment["filesz"]:
|
|
Fatal("PT_LOAD segment memsz != filesz: %s", segment)
|
|
|
|
if i > 0:
|
|
last_end = segments[i-1]["addr"] + segments[i-1]["memsz"]
|
|
if segment["addr"] < last_end:
|
|
Fatal("PT_LOAD segments out of order")
|
|
|
|
last_page = PageRoundDown(last_end)
|
|
start_page = PageRoundDown(segment["addr"])
|
|
if last_page >= start_page:
|
|
Fatal("PT_LOAD segments share a page: %s and %s", segment,
|
|
segments[i - 1])
|
|
|
|
|
|
# Matches the section name in readelf -SW output.
|
|
_SECTION_NAME_RE = re.compile(r"""^\s+\[\ ?\d+\]\s+
|
|
(?P<name>\.\S+)\s+
|
|
(?P<type>\S+)\s+
|
|
(?P<addr>[0-9a-f]+)\s+
|
|
(?P<off>[0-9a-f]+)\s+
|
|
(?P<size>[0-9a-f]+)""", re.VERBOSE)
|
|
|
|
|
|
def CheckData(vdso_path):
|
|
"""Verifies the VDSO contains no .data or .bss sections.
|
|
|
|
The readelf line format looks like:
|
|
|
|
There are 15 section headers, starting at offset 0x15f0:
|
|
|
|
Section Headers:
|
|
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
|
|
[ 0] NULL 0000000000000000 000000 000000 00 0 0 0
|
|
[ 1] .hash HASH ffffffffff700120 000120 000040 04 A 2 0 8
|
|
[ 2] .dynsym DYNSYM ffffffffff700160 000160 000108 18 A 3 1 8
|
|
...
|
|
[13] .strtab STRTAB 0000000000000000 001448 000123 00 0 0 1
|
|
[14] .shstrtab STRTAB 0000000000000000 00156b 000083 00 0 0 1
|
|
Key to Flags:
|
|
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
|
|
L (link order), O (extra OS processing required), G (group), T (TLS),
|
|
C (compressed), x (unknown), o (OS specific), E (exclude),
|
|
l (large), p (processor specific)
|
|
|
|
Args:
|
|
vdso_path: Path to VDSO binary.
|
|
"""
|
|
output = subprocess.check_output(["readelf", "-SW", vdso_path]).decode()
|
|
lines = output.split("\n")
|
|
|
|
found_text = False
|
|
for line in lines:
|
|
m = re.search(_SECTION_NAME_RE, line)
|
|
if not m:
|
|
continue
|
|
|
|
if not line.startswith(" ["):
|
|
continue
|
|
|
|
name = m.group("name")
|
|
size = int(m.group("size"), 16)
|
|
|
|
if name == ".text" and size != 0:
|
|
found_text = True
|
|
|
|
# Clang will typically omit these sections entirely; gcc will include them
|
|
# but with size 0.
|
|
if name.startswith(".data") and size != 0:
|
|
Fatal("VDSO contains non-empty .data section:\n%s" % output)
|
|
|
|
if name.startswith(".bss") and size != 0:
|
|
Fatal("VDSO contains non-empty .bss section:\n%s" % output)
|
|
|
|
if not found_text:
|
|
Fatal("VDSO contains no/empty .text section? Bad parsing?:\n%s" % output)
|
|
|
|
|
|
def CheckRelocs(vdso_path):
|
|
"""Verifies that the VDSO includes no relocations.
|
|
|
|
Args:
|
|
vdso_path: Path to VDSO binary.
|
|
"""
|
|
output = subprocess.check_output(["readelf", "-r", vdso_path]).decode()
|
|
if output.strip() != "There are no relocations in this file.":
|
|
Fatal("VDSO contains relocations: %s", output)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Verify VDSO ELF.")
|
|
parser.add_argument("--vdso", required=True, help="Path to VDSO ELF")
|
|
parser.add_argument(
|
|
"--check-data",
|
|
action="store_true",
|
|
help="Check that the ELF contains no .data or .bss sections")
|
|
args = parser.parse_args()
|
|
|
|
CheckSegments(args.vdso)
|
|
CheckRelocs(args.vdso)
|
|
|
|
if args.check_data:
|
|
CheckData(args.vdso)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|