summaryrefslogtreecommitdiffhomepage
path: root/vdso/check_vdso.py
diff options
context:
space:
mode:
Diffstat (limited to 'vdso/check_vdso.py')
-rw-r--r--vdso/check_vdso.py204
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..f1288e0c2
--- /dev/null
+++ b/vdso/check_vdso.py
@@ -0,0 +1,204 @@
+# Copyright 2018 Google Inc.
+#
+# 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])
+ 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])
+ 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])
+ 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()