1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 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()
|