diff options
Diffstat (limited to 'vdso/check_vdso.py')
-rw-r--r-- | vdso/check_vdso.py | 204 |
1 files changed, 204 insertions, 0 deletions
diff --git a/vdso/check_vdso.py b/vdso/check_vdso.py new file mode 100644 index 000000000..b3ee574f3 --- /dev/null +++ b/vdso/check_vdso.py @@ -0,0 +1,204 @@ +# 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 the 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() |