Compare commits

..

1 commit

Author SHA1 Message Date
Daniel Micay
3c012a69c4 temporary workaround for Pixel 3 camera UAF 2019-03-26 01:46:08 -04:00
87 changed files with 2272 additions and 6335 deletions

View file

@ -1,2 +0,0 @@
Checks: 'bugprone-*,-bugprone-easily-swappable-parameters,-bugprone-macro-parentheses,-bugprone-too-small-loop-variable,cert-*,-cert-err33-c,clang-analyzer-*,-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,-clang-diagnostic-constant-logical-operand,readability-*,-readability-function-cognitive-complexity,-readability-identifier-length,-readability-inconsistent-declaration-parameter-name,-readability-magic-numbers,-readability-named-parameter,llvm-include-order,misc-*'
WarningsAsErrors: '*'

View file

@ -1,7 +0,0 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: daily
target-branch: main

View file

@ -1,55 +0,0 @@
name: Build and run tests
on:
push:
pull_request:
schedule:
- cron: '0 2 * * *'
jobs:
build-ubuntu-gcc:
runs-on: ubuntu-latest
strategy:
matrix:
version: [12, 13, 14]
steps:
- uses: actions/checkout@v4
- name: Setting up gcc version
run: |
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-${{ matrix.version }} 100
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-${{ matrix.version }} 100
- name: Build
run: make test
build-ubuntu-clang:
runs-on: ubuntu-latest
strategy:
matrix:
version: [14, 15, 16, 17, 18]
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends clang-14 clang-15
- name: Setting up clang version
run: |
sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-${{ matrix.version }} 100
sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-${{ matrix.version }} 100
- name: Build
run: CC=clang CXX=clang++ make test
build-musl:
runs-on: ubuntu-latest
container:
image: alpine:latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: apk update && apk add build-base python3
- name: Build
run: make test
build-ubuntu-gcc-aarch64:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends gcc-aarch64-linux-gnu g++-aarch64-linux-gnu libgcc-s1-arm64-cross cpp-aarch64-linux-gnu
- name: Build
run: CC=aarch64-linux-gnu-gcc CXX=aarch64-linux-gnu-gcc++ make CONFIG_NATIVE=false

4
.gitignore vendored
View file

@ -1,2 +1,2 @@
out/ *.o
out-light/ *.so

View file

@ -1,14 +1,15 @@
common_cflags = [ common_cflags = [
"-pipe",
"-O3", "-O3",
//"-flto", //"-flto",
"-fPIC", "-fPIC",
"-fvisibility=hidden", "-fvisibility=hidden",
//"-fno-plt", //"-fno-plt",
"-pipe",
"-Wall",
"-Wextra",
"-Wcast-align", "-Wcast-align",
"-Wcast-qual", "-Wcast-qual",
"-Wwrite-strings", "-Wwrite-strings",
"-Werror",
"-DH_MALLOC_PREFIX", "-DH_MALLOC_PREFIX",
"-DZERO_ON_FREE=true", "-DZERO_ON_FREE=true",
"-DWRITE_AFTER_FREE_CHECK=true", "-DWRITE_AFTER_FREE_CHECK=true",
@ -16,25 +17,21 @@ common_cflags = [
"-DSLAB_CANARY=true", "-DSLAB_CANARY=true",
"-DSLAB_QUARANTINE_RANDOM_LENGTH=1", "-DSLAB_QUARANTINE_RANDOM_LENGTH=1",
"-DSLAB_QUARANTINE_QUEUE_LENGTH=1", "-DSLAB_QUARANTINE_QUEUE_LENGTH=1",
"-DCONFIG_EXTENDED_SIZE_CLASSES=true",
"-DCONFIG_LARGE_SIZE_CLASSES=true",
"-DGUARD_SLABS_INTERVAL=1", "-DGUARD_SLABS_INTERVAL=1",
"-DGUARD_SIZE_DIVISOR=2", "-DGUARD_SIZE_DIVISOR=2",
"-DREGION_QUARANTINE_RANDOM_LENGTH=256", "-DREGION_QUARANTINE_RANDOM_LENGTH=128",
"-DREGION_QUARANTINE_QUEUE_LENGTH=1024", "-DREGION_QUARANTINE_QUEUE_LENGTH=1024",
"-DREGION_QUARANTINE_SKIP_THRESHOLD=33554432", // 32MiB "-DREGION_QUARANTINE_SKIP_THRESHOLD=33554432", // 32MiB
"-DFREE_SLABS_QUARANTINE_RANDOM_LENGTH=32", "-DFREE_SLABS_QUARANTINE_RANDOM_LENGTH=32",
"-DCONFIG_CLASS_REGION_SIZE=34359738368", // 32GiB "-DCONFIG_CLASS_REGION_SIZE=1073741824", // 1GiB
"-DN_ARENA=1", "-DN_ARENA=1",
"-DCONFIG_STATS=true",
"-DCONFIG_SELF_INIT=false",
] ]
cc_defaults { cc_defaults {
name: "hardened_malloc_defaults", name: "hardened_malloc_defaults",
defaults: ["linux_bionic_supported"], defaults: ["linux_bionic_supported"],
cflags: common_cflags, cflags: common_cflags,
conlyflags: ["-std=c17", "-Wmissing-prototypes"], conlyflags: ["-std=c11", "-Wmissing-prototypes"],
stl: "none", stl: "none",
} }
@ -47,35 +44,13 @@ lib_src_files = [
"util.c", "util.c",
] ]
cc_library { cc_library_static {
name: "libhardened_malloc", name: "libhardened_malloc",
ramdisk_available: true,
vendor_ramdisk_available: true,
recovery_available: true,
defaults: ["hardened_malloc_defaults"], defaults: ["hardened_malloc_defaults"],
srcs: lib_src_files, srcs: lib_src_files,
export_include_dirs: ["include"],
static_libs: ["libasync_safe"],
target: {
android: {
shared: {
enabled: false,
},
system_shared_libs: [],
},
linux_bionic: {
system_shared_libs: [],
},
},
product_variables: { product_variables: {
debuggable: { debuggable: {
cflags: ["-DLABEL_MEMORY"], cflags: ["-DLABEL_MEMORY"],
}, },
device_has_arm_mte: {
cflags: ["-DHAS_ARM_MTE", "-march=armv8-a+dotprod+memtag"]
},
}, },
apex_available: [
"com.android.runtime",
],
} }

232
CREDITS
View file

@ -4,7 +4,7 @@ chacha.c is a simple conversion of chacha-merged.c to a keystream-only implement
D. J. Bernstein D. J. Bernstein
Public domain. Public domain.
h_malloc.c open-addressed hash table (regions_grow, regions_insert, regions_find, regions_delete): malloc.c open-addressed hash table (regions_grow, regions_insert, regions_find, regions_delete):
Copyright (c) 2008, 2010, 2011, 2016 Otto Moerbeek <otto@drijf.net> Copyright (c) 2008, 2010, 2011, 2016 Otto Moerbeek <otto@drijf.net>
Copyright (c) 2012 Matthew Dempsky <matthew@openbsd.org> Copyright (c) 2012 Matthew Dempsky <matthew@openbsd.org>
@ -25,8 +25,7 @@ h_malloc.c open-addressed hash table (regions_grow, regions_insert, regions_find
libdivide: libdivide:
Copyright (C) 2010 - 2019 ridiculous_fish, <libdivide@ridiculousfish.com> Copyright (C) 2010 ridiculous_fish
Copyright (C) 2016 - 2019 Kim Walisch, <kim.walisch@gmail.com>
Boost Software License - Version 1.0 - August 17th, 2003 Boost Software License - Version 1.0 - August 17th, 2003
@ -54,230 +53,3 @@ libdivide:
random.c get_random_{type}_uniform functions are based on Fast Random Integer random.c get_random_{type}_uniform functions are based on Fast Random Integer
Generation in an Interval by Daniel Lemire Generation in an Interval by Daniel Lemire
arm_mte.h arm_mte_tag_and_clear_mem function contents were copied from storeTags function in scudo:
==============================================================================
The LLVM Project is under the Apache License v2.0 with LLVM Exceptions:
==============================================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.
---- LLVM Exceptions to the Apache 2.0 License ----
As an exception, if, as a result of your compiling your source code, portions
of this Software are embedded into an Object form of such source code, you
may redistribute such embedded portions in such Object form without complying
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
In addition, if you combine or link compiled forms of this Software with
software that is licensed under the GPLv2 ("Combined Software") and if a
court of competent jurisdiction determines that the patent provision (Section
3), the indemnity provision (Section 9) or other Section of the License
conflicts with the conditions of the GPLv2, you may retroactively and
prospectively choose to deem waived or otherwise exclude such Section(s) of
the License, but only in their entirety and only with respect to the Combined
Software.
==============================================================================

View file

@ -16,8 +16,6 @@ Somewhat important and an easy sell:
* also needed by jemalloc for different reasons * also needed by jemalloc for different reasons
* not needed if the kernel gets first class support for arbitrarily sized * not needed if the kernel gets first class support for arbitrarily sized
guard pages and a virtual memory quarantine feature guard pages and a virtual memory quarantine feature
* `MREMAP_DONTUNMAP` is now available but doesn't support expanding the
mapping which may be an issue due to VMA merging being unreliable
Fairly infeasible to land but could reduce overhead and extend coverage of Fairly infeasible to land but could reduce overhead and extend coverage of
security features to other code directly using mmap: security features to other code directly using mmap:

View file

@ -1,4 +1,4 @@
Copyright © 2018-2025 GrapheneOS Copyright (c) 2019 Daniel Micay
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

131
Makefile
View file

@ -1,60 +1,54 @@
VARIANT := default CONFIG_NATIVE := true
CONFIG_CXX_ALLOCATOR := true
ifneq ($(VARIANT),) CONFIG_UBSAN := false
CONFIG_FILE := config/$(VARIANT).mk CONFIG_SEAL_METADATA := false
include config/$(VARIANT).mk CONFIG_ZERO_ON_FREE := true
endif CONFIG_WRITE_AFTER_FREE_CHECK := true
CONFIG_SLOT_RANDOMIZE := true
ifeq ($(VARIANT),default) CONFIG_SLAB_CANARY := true
SUFFIX := CONFIG_SLAB_QUARANTINE_RANDOM_LENGTH := 1
else CONFIG_SLAB_QUARANTINE_QUEUE_LENGTH := 1
SUFFIX := -$(VARIANT) CONFIG_GUARD_SLABS_INTERVAL := 1
endif CONFIG_GUARD_SIZE_DIVISOR := 2
CONFIG_REGION_QUARANTINE_RANDOM_LENGTH := 128
OUT := out$(SUFFIX) CONFIG_REGION_QUARANTINE_QUEUE_LENGTH := 1024
CONFIG_REGION_QUARANTINE_SKIP_THRESHOLD := 33554432 # 32MiB
CONFIG_FREE_SLABS_QUARANTINE_RANDOM_LENGTH := 32
CONFIG_CLASS_REGION_SIZE := 137438953472 # 128GiB
CONFIG_N_ARENA = 1
define safe_flag define safe_flag
$(shell $(CC) $(if $(filter clang%,$(CC)),-Werror=unknown-warning-option) -E $1 - </dev/null >/dev/null 2>&1 && echo $1 || echo $2) $(shell $(CC) -E $1 - </dev/null >/dev/null 2>&1 && echo $1 || echo $2)
endef endef
CPPFLAGS := $(CPPFLAGS) -D_GNU_SOURCE -I include CPPFLAGS := -D_GNU_SOURCE
SHARED_FLAGS := -pipe -O3 -flto -fPIC -fvisibility=hidden -fno-plt \ SHARED_FLAGS := -O3 -flto -fPIC -fvisibility=hidden -fno-plt -pipe -Wall -Wextra $(call safe_flag,-Wcast-align=strict) -Wcast-qual -Wwrite-strings
-fstack-clash-protection $(call safe_flag,-fcf-protection) -fstack-protector-strong \
-Wall -Wextra $(call safe_flag,-Wcast-align=strict,-Wcast-align) -Wcast-qual -Wwrite-strings \
-Wundef
ifeq ($(CONFIG_WERROR),true)
SHARED_FLAGS += -Werror
endif
ifeq ($(CONFIG_NATIVE),true) ifeq ($(CONFIG_NATIVE),true)
SHARED_FLAGS += -march=native SHARED_FLAGS += -march=native
endif endif
ifeq ($(CONFIG_UBSAN),true) CFLAGS := -std=c11 $(SHARED_FLAGS) -Wmissing-prototypes
SHARED_FLAGS += -fsanitize=undefined -fno-sanitize-recover=undefined CXXFLAGS := $(call safe_flag,-std=c++17,-std=c++14) $(SHARED_FLAGS)
endif LDFLAGS := -Wl,--as-needed,-z,defs,-z,relro,-z,now,-z,nodlopen,-z,text
TIDY_CHECKS := -checks=bugprone-*,-bugprone-macro-parentheses,cert-*,clang-analyzer-*,readability-*,-readability-inconsistent-declaration-parameter-name,-readability-named-parameter
CFLAGS := $(CFLAGS) -std=c17 $(SHARED_FLAGS) -Wmissing-prototypes -Wstrict-prototypes
CXXFLAGS := $(CXXFLAGS) -std=c++17 -fsized-deallocation $(SHARED_FLAGS)
LDFLAGS := $(LDFLAGS) -Wl,-O1,--as-needed,-z,defs,-z,relro,-z,now,-z,nodlopen,-z,text
SOURCES := chacha.c h_malloc.c memory.c pages.c random.c util.c SOURCES := chacha.c h_malloc.c memory.c pages.c random.c util.c
OBJECTS := $(SOURCES:.c=.o) OBJECTS := $(SOURCES:.c=.o)
ifeq ($(CONFIG_CXX_ALLOCATOR),true) ifeq ($(CONFIG_CXX_ALLOCATOR),true)
# make sure LTO is compatible in case CC and CXX don't match (such as clang and g++)
CXX := $(CC)
LDLIBS += -lstdc++ LDLIBS += -lstdc++
SOURCES += new.cc SOURCES += new.cc
OBJECTS += new.o OBJECTS += new.o
endif endif
OBJECTS := $(addprefix $(OUT)/,$(OBJECTS)) ifeq ($(CONFIG_UBSAN),true)
CFLAGS += -fsanitize=undefined
CXXFLAGS += -fsanitize=undefined
endif
ifeq (,$(filter $(CONFIG_SEAL_METADATA),true false)) ifeq ($(CONFIG_SEAL_METADATA),true)
$(error CONFIG_SEAL_METADATA must be true or false) CPPFLAGS += -DCONFIG_SEAL_METADATA
endif endif
ifeq (,$(filter $(CONFIG_ZERO_ON_FREE),true false)) ifeq (,$(filter $(CONFIG_ZERO_ON_FREE),true false))
@ -73,32 +67,13 @@ ifeq (,$(filter $(CONFIG_SLAB_CANARY),true false))
$(error CONFIG_SLAB_CANARY must be true or false) $(error CONFIG_SLAB_CANARY must be true or false)
endif endif
ifeq (,$(filter $(CONFIG_EXTENDED_SIZE_CLASSES),true false))
$(error CONFIG_EXTENDED_SIZE_CLASSES must be true or false)
endif
ifeq (,$(filter $(CONFIG_LARGE_SIZE_CLASSES),true false))
$(error CONFIG_LARGE_SIZE_CLASSES must be true or false)
endif
ifeq (,$(filter $(CONFIG_STATS),true false))
$(error CONFIG_STATS must be true or false)
endif
ifeq (,$(filter $(CONFIG_SELF_INIT),true false))
$(error CONFIG_SELF_INIT must be true or false)
endif
CPPFLAGS += \ CPPFLAGS += \
-DCONFIG_SEAL_METADATA=$(CONFIG_SEAL_METADATA) \
-DZERO_ON_FREE=$(CONFIG_ZERO_ON_FREE) \ -DZERO_ON_FREE=$(CONFIG_ZERO_ON_FREE) \
-DWRITE_AFTER_FREE_CHECK=$(CONFIG_WRITE_AFTER_FREE_CHECK) \ -DWRITE_AFTER_FREE_CHECK=$(CONFIG_WRITE_AFTER_FREE_CHECK) \
-DSLOT_RANDOMIZE=$(CONFIG_SLOT_RANDOMIZE) \ -DSLOT_RANDOMIZE=$(CONFIG_SLOT_RANDOMIZE) \
-DSLAB_CANARY=$(CONFIG_SLAB_CANARY) \ -DSLAB_CANARY=$(CONFIG_SLAB_CANARY) \
-DSLAB_QUARANTINE_RANDOM_LENGTH=$(CONFIG_SLAB_QUARANTINE_RANDOM_LENGTH) \ -DSLAB_QUARANTINE_RANDOM_LENGTH=$(CONFIG_SLAB_QUARANTINE_RANDOM_LENGTH) \
-DSLAB_QUARANTINE_QUEUE_LENGTH=$(CONFIG_SLAB_QUARANTINE_QUEUE_LENGTH) \ -DSLAB_QUARANTINE_QUEUE_LENGTH=$(CONFIG_SLAB_QUARANTINE_QUEUE_LENGTH) \
-DCONFIG_EXTENDED_SIZE_CLASSES=$(CONFIG_EXTENDED_SIZE_CLASSES) \
-DCONFIG_LARGE_SIZE_CLASSES=$(CONFIG_LARGE_SIZE_CLASSES) \
-DGUARD_SLABS_INTERVAL=$(CONFIG_GUARD_SLABS_INTERVAL) \ -DGUARD_SLABS_INTERVAL=$(CONFIG_GUARD_SLABS_INTERVAL) \
-DGUARD_SIZE_DIVISOR=$(CONFIG_GUARD_SIZE_DIVISOR) \ -DGUARD_SIZE_DIVISOR=$(CONFIG_GUARD_SIZE_DIVISOR) \
-DREGION_QUARANTINE_RANDOM_LENGTH=$(CONFIG_REGION_QUARANTINE_RANDOM_LENGTH) \ -DREGION_QUARANTINE_RANDOM_LENGTH=$(CONFIG_REGION_QUARANTINE_RANDOM_LENGTH) \
@ -106,43 +81,23 @@ CPPFLAGS += \
-DREGION_QUARANTINE_SKIP_THRESHOLD=$(CONFIG_REGION_QUARANTINE_SKIP_THRESHOLD) \ -DREGION_QUARANTINE_SKIP_THRESHOLD=$(CONFIG_REGION_QUARANTINE_SKIP_THRESHOLD) \
-DFREE_SLABS_QUARANTINE_RANDOM_LENGTH=$(CONFIG_FREE_SLABS_QUARANTINE_RANDOM_LENGTH) \ -DFREE_SLABS_QUARANTINE_RANDOM_LENGTH=$(CONFIG_FREE_SLABS_QUARANTINE_RANDOM_LENGTH) \
-DCONFIG_CLASS_REGION_SIZE=$(CONFIG_CLASS_REGION_SIZE) \ -DCONFIG_CLASS_REGION_SIZE=$(CONFIG_CLASS_REGION_SIZE) \
-DN_ARENA=$(CONFIG_N_ARENA) \ -DN_ARENA=$(CONFIG_N_ARENA)
-DCONFIG_STATS=$(CONFIG_STATS) \
-DCONFIG_SELF_INIT=$(CONFIG_SELF_INIT)
$(OUT)/libhardened_malloc$(SUFFIX).so: $(OBJECTS) | $(OUT) libhardened_malloc.so: $(OBJECTS)
$(CC) $(CFLAGS) $(LDFLAGS) -shared $^ $(LDLIBS) -o $@ $(CC) $(CFLAGS) $(LDFLAGS) -shared $^ $(LDLIBS) -o $@
$(OUT): chacha.o: chacha.c chacha.h util.h
mkdir -p $(OUT) h_malloc.o: h_malloc.c h_malloc.h mutex.h memory.h pages.h random.h util.h
memory.o: memory.c memory.h util.h
$(OUT)/chacha.o: chacha.c chacha.h util.h $(CONFIG_FILE) | $(OUT) new.o: new.cc h_malloc.h util.h
$(COMPILE.c) $(OUTPUT_OPTION) $< pages.o: pages.c pages.h memory.h util.h
$(OUT)/h_malloc.o: h_malloc.c include/h_malloc.h mutex.h memory.h pages.h random.h util.h $(CONFIG_FILE) | $(OUT) random.o: random.c random.h chacha.h util.h
$(COMPILE.c) $(OUTPUT_OPTION) $< util.o: util.c util.h
$(OUT)/memory.o: memory.c memory.h util.h $(CONFIG_FILE) | $(OUT)
$(COMPILE.c) $(OUTPUT_OPTION) $<
$(OUT)/new.o: new.cc include/h_malloc.h util.h $(CONFIG_FILE) | $(OUT)
$(COMPILE.cc) $(OUTPUT_OPTION) $<
$(OUT)/pages.o: pages.c pages.h memory.h util.h $(CONFIG_FILE) | $(OUT)
$(COMPILE.c) $(OUTPUT_OPTION) $<
$(OUT)/random.o: random.c random.h chacha.h util.h $(CONFIG_FILE) | $(OUT)
$(COMPILE.c) $(OUTPUT_OPTION) $<
$(OUT)/util.o: util.c util.h $(CONFIG_FILE) | $(OUT)
$(COMPILE.c) $(OUTPUT_OPTION) $<
check: tidy
tidy: tidy:
clang-tidy --extra-arg=-std=c17 $(filter %.c,$(SOURCES)) -- $(CPPFLAGS) clang-tidy $(TIDY_CHECKS) $(SOURCES) -- $(CPPFLAGS)
clang-tidy --extra-arg=-std=c++17 $(filter %.cc,$(SOURCES)) -- $(CPPFLAGS)
clean: clean:
rm -f $(OUT)/libhardened_malloc.so $(OBJECTS) rm -f libhardened_malloc.so $(OBJECTS)
$(MAKE) -C test/ clean
test: $(OUT)/libhardened_malloc$(SUFFIX).so .PHONY: clean tidy
$(MAKE) -C test/
python3 -m unittest discover --start-directory test/
.PHONY: check clean tidy test

798
README.md

File diff suppressed because it is too large Load diff

View file

@ -1,25 +0,0 @@
java_test_host {
name: "HMallocTest",
srcs: [
"src/**/*.java",
],
libs: [
"tradefed",
"compatibility-tradefed",
"compatibility-host-util",
],
static_libs: [
"cts-host-utils",
"frameworks-base-hostutils",
],
test_suites: [
"general-tests",
],
data_device_bins_64: [
"memtag_test",
],
}

View file

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration description="hardened_malloc test">
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
<option name="cleanup" value="true" />
<option name="push" value="memtag_test->/data/local/tmp/memtag_test" />
</target_preparer>
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="HMallocTest.jar" />
</test>
</configuration>

View file

@ -1,17 +0,0 @@
cc_test {
name: "memtag_test",
srcs: ["memtag_test.cc"],
cflags: [
"-Wall",
"-Werror",
"-Wextra",
"-O0",
"-march=armv9-a+memtag",
],
compile_multilib: "64",
sanitize: {
memtag_heap: true,
},
}

View file

@ -1,351 +0,0 @@
// needed to uncondionally enable assertions
#undef NDEBUG
#include <assert.h>
#include <malloc.h>
#include <signal.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <map>
#include <set>
#include <string>
#include <unordered_map>
#include "../../arm_mte.h"
using namespace std;
using u8 = uint8_t;
using uptr = uintptr_t;
using u64 = uint64_t;
const size_t DEFAULT_ALLOC_SIZE = 8;
const size_t CANARY_SIZE = 8;
void do_context_switch() {
utsname s;
uname(&s);
}
u8 get_pointer_tag(void *ptr) {
return (((uptr) ptr) >> 56) & 0xf;
}
void *untag_pointer(void *ptr) {
const uintptr_t mask = UINTPTR_MAX >> 8;
return (void *) ((uintptr_t) ptr & mask);
}
void *set_pointer_tag(void *ptr, u8 tag) {
return (void *) (((uintptr_t) tag << 56) | (uintptr_t) untag_pointer(ptr));
}
// This test checks that slab slot allocation uses tag that is distint from tags of its neighbors
// and from the tag of the previous allocation that used the same slot
void tag_distinctness() {
// tag 0 is reserved
const int min_tag = 1;
const int max_tag = 0xf;
struct SizeClass {
int size;
int slot_cnt;
};
// values from size_classes[] and size_class_slots[] in h_malloc.c
SizeClass size_classes[] = {
{ .size = 16, .slot_cnt = 256, },
{ .size = 32, .slot_cnt = 128, },
// this size class is used by allocations that are made by the addr_tag_map, which breaks
// tag distinctess checks
// { .size = 48, .slot_cnt = 85, },
{ .size = 64, .slot_cnt = 64, },
{ .size = 80, .slot_cnt = 51, },
{ .size = 96, .slot_cnt = 42, },
{ .size = 112, .slot_cnt = 36, },
{ .size = 128, .slot_cnt = 64, },
{ .size = 160, .slot_cnt = 51, },
{ .size = 192, .slot_cnt = 64, },
{ .size = 224, .slot_cnt = 54, },
{ .size = 10240, .slot_cnt = 6, },
{ .size = 20480, .slot_cnt = 1, },
};
int tag_usage[max_tag + 1];
for (size_t sc_idx = 0; sc_idx < sizeof(size_classes) / sizeof(SizeClass); ++sc_idx) {
SizeClass &sc = size_classes[sc_idx];
const size_t full_alloc_size = sc.size;
const size_t alloc_size = full_alloc_size - CANARY_SIZE;
// "tdc" is short for "tag distinctness check"
int left_neighbor_tdc_cnt = 0;
int right_neighbor_tdc_cnt = 0;
int prev_alloc_tdc_cnt = 0;
int iter_cnt = 600;
unordered_map<uptr, u8> addr_tag_map;
addr_tag_map.reserve(iter_cnt * sc.slot_cnt);
u64 seen_tags = 0;
for (int iter = 0; iter < iter_cnt; ++iter) {
uptr allocations[256]; // 256 is max slot count
for (int i = 0; i < sc.slot_cnt; ++i) {
u8 *p = (u8 *) malloc(alloc_size);
assert(p);
uptr addr = (uptr) untag_pointer(p);
u8 tag = get_pointer_tag(p);
assert(tag >= min_tag && tag <= max_tag);
seen_tags |= 1 << tag;
++tag_usage[tag];
// check most recent tags of left and right neighbors
auto left = addr_tag_map.find(addr - full_alloc_size);
if (left != addr_tag_map.end()) {
assert(left->second != tag);
++left_neighbor_tdc_cnt;
}
auto right = addr_tag_map.find(addr + full_alloc_size);
if (right != addr_tag_map.end()) {
assert(right->second != tag);
++right_neighbor_tdc_cnt;
}
// check previous tag of this slot
auto prev = addr_tag_map.find(addr);
if (prev != addr_tag_map.end()) {
assert(prev->second != tag);
++prev_alloc_tdc_cnt;
addr_tag_map.erase(addr);
}
addr_tag_map.emplace(addr, tag);
for (size_t j = 0; j < alloc_size; ++j) {
// check that slot is zeroed
assert(p[j] == 0);
// check that slot is readable and writable
p[j]++;
}
allocations[i] = addr;
}
// free some of allocations to allow their slots to be reused
for (int i = sc.slot_cnt - 1; i >= 0; i -= 2) {
free((void *) allocations[i]);
}
}
// check that all of the tags were used, except for the reserved tag 0
assert(seen_tags == (0xffff & ~(1 << 0)));
printf("size_class\t%i\t" "tdc_left %i\t" "tdc_right %i\t" "tdc_prev_alloc %i\n",
sc.size, left_neighbor_tdc_cnt, right_neighbor_tdc_cnt, prev_alloc_tdc_cnt);
// make sure tag distinctess checks were actually performed
int min_tdc_cnt = sc.slot_cnt * iter_cnt / 5;
assert(prev_alloc_tdc_cnt > min_tdc_cnt);
if (sc.slot_cnt > 1) {
assert(left_neighbor_tdc_cnt > min_tdc_cnt);
assert(right_neighbor_tdc_cnt > min_tdc_cnt);
}
// async tag check failures are reported on context switch
do_context_switch();
}
printf("\nTag use counters:\n");
int min = INT_MAX;
int max = 0;
double geomean = 0.0;
for (int i = min_tag; i <= max_tag; ++i) {
int v = tag_usage[i];
geomean += log(v);
min = std::min(min, v);
max = std::max(max, v);
printf("%i\t%i\n", i, tag_usage[i]);
}
int tag_cnt = 1 + max_tag - min_tag;
geomean = exp(geomean / tag_cnt);
double max_deviation = std::max((double) max - geomean, geomean - min);
printf("geomean: %.2f, max deviation from geomean: %.2f%%\n", geomean, (100.0 * max_deviation) / geomean);
}
u8* alloc_default() {
const size_t full_alloc_size = DEFAULT_ALLOC_SIZE + CANARY_SIZE;
set<uptr> addrs;
// make sure allocation has both left and right neighbors, otherwise overflow/underflow tests
// will fail when allocation is at the end/beginning of slab
for (;;) {
u8 *p = (u8 *) malloc(DEFAULT_ALLOC_SIZE);
assert(p);
uptr addr = (uptr) untag_pointer(p);
uptr left = addr - full_alloc_size;
if (addrs.find(left) != addrs.end()) {
uptr right = addr + full_alloc_size;
if (addrs.find(right) != addrs.end()) {
return p;
}
}
addrs.emplace(addr);
}
}
int expected_segv_code;
#define expect_segv(exp, segv_code) ({\
expected_segv_code = segv_code; \
volatile auto val = exp; \
(void) val; \
do_context_switch(); \
fprintf(stderr, "didn't receive SEGV code %i", segv_code); \
exit(1); })
// it's expected that the device is configured to use asymm MTE tag checking mode (sync read checks,
// async write checks)
#define expect_read_segv(exp) expect_segv(exp, SEGV_MTESERR)
#define expect_write_segv(exp) expect_segv(exp, SEGV_MTEAERR)
void read_after_free() {
u8 *p = alloc_default();
free(p);
expect_read_segv(p[0]);
}
void write_after_free() {
u8 *p = alloc_default();
free(p);
expect_write_segv(p[0] = 1);
}
void underflow_read() {
u8 *p = alloc_default();
expect_read_segv(p[-1]);
}
void underflow_write() {
u8 *p = alloc_default();
expect_write_segv(p[-1] = 1);
}
void overflow_read() {
u8 *p = alloc_default();
expect_read_segv(p[DEFAULT_ALLOC_SIZE + CANARY_SIZE]);
}
void overflow_write() {
u8 *p = alloc_default();
expect_write_segv(p[DEFAULT_ALLOC_SIZE + CANARY_SIZE] = 1);
}
void untagged_read() {
u8 *p = alloc_default();
p = (u8 *) untag_pointer(p);
expect_read_segv(p[0]);
}
void untagged_write() {
u8 *p = alloc_default();
p = (u8 *) untag_pointer(p);
expect_write_segv(p[0] = 1);
}
// checks that each of memory locations inside the buffer is tagged with expected_tag
void check_tag(void *buf, size_t len, u8 expected_tag) {
for (size_t i = 0; i < len; ++i) {
assert(get_pointer_tag(__arm_mte_get_tag((void *) ((uintptr_t) buf + i))) == expected_tag);
}
}
void madvise_dontneed() {
const size_t len = 100'000;
void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE | PROT_MTE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
assert(ptr != MAP_FAILED);
// check that 0 is the initial tag
check_tag(ptr, len, 0);
arm_mte_tag_and_clear_mem(set_pointer_tag(ptr, 1), len);
check_tag(ptr, len, 1);
memset(set_pointer_tag(ptr, 1), 1, len);
assert(madvise(ptr, len, MADV_DONTNEED) == 0);
// check that MADV_DONTNEED resets the tag
check_tag(ptr, len, 0);
// check that MADV_DONTNEED clears the memory
for (size_t i = 0; i < len; ++i) {
assert(((u8 *) ptr)[i] == 0);
}
// check that mistagged read after MADV_DONTNEED fails
expect_read_segv(*((u8 *) set_pointer_tag(ptr, 1)));
}
map<string, function<void()>> tests = {
#define TEST(s) { #s, s }
TEST(tag_distinctness),
TEST(read_after_free),
TEST(write_after_free),
TEST(overflow_read),
TEST(overflow_write),
TEST(underflow_read),
TEST(underflow_write),
TEST(untagged_read),
TEST(untagged_write),
TEST(madvise_dontneed),
#undef TEST
};
void segv_handler(int, siginfo_t *si, void *) {
if (expected_segv_code == 0 || expected_segv_code != si->si_code) {
fprintf(stderr, "received unexpected SEGV_CODE %i", si->si_code);
exit(139); // standard exit code for SIGSEGV
}
exit(0);
}
int main(int argc, char **argv) {
setbuf(stdout, NULL);
assert(argc == 2);
auto test_name = string(argv[1]);
auto test_fn = tests[test_name];
assert(test_fn != nullptr);
assert(mallopt(M_BIONIC_SET_HEAP_TAGGING_LEVEL, M_HEAP_TAGGING_LEVEL_ASYNC) == 1);
struct sigaction sa = {
.sa_sigaction = segv_handler,
.sa_flags = SA_SIGINFO,
};
assert(sigaction(SIGSEGV, &sa, nullptr) == 0);
test_fn();
do_context_switch();
return 0;
}

View file

@ -1,79 +0,0 @@
package grapheneos.hmalloc;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import static org.junit.Assert.assertEquals;
@RunWith(DeviceJUnit4ClassRunner.class)
public class MemtagTest extends BaseHostJUnit4Test {
private static final String TEST_BINARY = "/data/local/tmp/memtag_test";
private void runTest(String name) throws DeviceNotAvailableException {
var args = new ArrayList<String>();
args.add(TEST_BINARY);
args.add(name);
String cmdLine = String.join(" ", args);
var result = getDevice().executeShellV2Command(cmdLine);
assertEquals("stderr", "", result.getStderr());
assertEquals("process exit code", 0, result.getExitCode().intValue());
}
@Test
public void tag_distinctness() throws DeviceNotAvailableException {
runTest("tag_distinctness");
}
@Test
public void read_after_free() throws DeviceNotAvailableException {
runTest("read_after_free");
}
@Test
public void write_after_free() throws DeviceNotAvailableException {
runTest("write_after_free");
}
@Test
public void underflow_read() throws DeviceNotAvailableException {
runTest("underflow_read");
}
@Test
public void underflow_write() throws DeviceNotAvailableException {
runTest("underflow_write");
}
@Test
public void overflow_read() throws DeviceNotAvailableException {
runTest("overflow_read");
}
@Test
public void overflow_write() throws DeviceNotAvailableException {
runTest("overflow_write");
}
@Test
public void untagged_read() throws DeviceNotAvailableException {
runTest("untagged_read");
}
@Test
public void untagged_write() throws DeviceNotAvailableException {
runTest("untagged_write");
}
@Test
public void madvise_dontneed() throws DeviceNotAvailableException {
runTest("madvise_dontneed");
}
}

View file

@ -1,91 +0,0 @@
#ifndef ARM_MTE_H
#define ARM_MTE_H
#include <arm_acle.h>
#include <stdint.h>
// Returns a tagged pointer.
// See https://developer.arm.com/documentation/ddi0602/2023-09/Base-Instructions/IRG--Insert-Random-Tag-
static inline void *arm_mte_create_random_tag(void *p, uint64_t exclusion_mask) {
return __arm_mte_create_random_tag(p, exclusion_mask);
}
// Tag the memory region with the tag specified in tag bits of tagged_ptr. Memory region itself is
// zeroed.
// tagged_ptr has to be aligned by 16, and len has to be a multiple of 16 (tag granule size).
//
// Arm's software optimization guide says:
// "it is recommended to use STZGM (or DCZGVA) to set tag if data is not a concern." (STZGM and
// DCGZVA are zeroing variants of tagging instructions).
//
// Contents of this function were copied from scudo:
// https://android.googlesource.com/platform/external/scudo/+/refs/tags/android-14.0.0_r1/standalone/memtag.h#167
//
// scudo is licensed under the Apache License v2.0 with LLVM Exceptions, which is compatible with
// the hardened_malloc's MIT license
static inline void arm_mte_tag_and_clear_mem(void *tagged_ptr, size_t len) {
uintptr_t Begin = (uintptr_t) tagged_ptr;
uintptr_t End = Begin + len;
uintptr_t LineSize, Next, Tmp;
__asm__ __volatile__(
".arch_extension memtag \n\t"
// Compute the cache line size in bytes (DCZID_EL0 stores it as the log2
// of the number of 4-byte words) and bail out to the slow path if DCZID_EL0
// indicates that the DC instructions are unavailable.
"DCZID .req %[Tmp] \n\t"
"mrs DCZID, dczid_el0 \n\t"
"tbnz DCZID, #4, 3f \n\t"
"and DCZID, DCZID, #15 \n\t"
"mov %[LineSize], #4 \n\t"
"lsl %[LineSize], %[LineSize], DCZID \n\t"
".unreq DCZID \n\t"
// Our main loop doesn't handle the case where we don't need to perform any
// DC GZVA operations. If the size of our tagged region is less than
// twice the cache line size, bail out to the slow path since it's not
// guaranteed that we'll be able to do a DC GZVA.
"Size .req %[Tmp] \n\t"
"sub Size, %[End], %[Cur] \n\t"
"cmp Size, %[LineSize], lsl #1 \n\t"
"b.lt 3f \n\t"
".unreq Size \n\t"
"LineMask .req %[Tmp] \n\t"
"sub LineMask, %[LineSize], #1 \n\t"
// STZG until the start of the next cache line.
"orr %[Next], %[Cur], LineMask \n\t"
"1:\n\t"
"stzg %[Cur], [%[Cur]], #16 \n\t"
"cmp %[Cur], %[Next] \n\t"
"b.lt 1b \n\t"
// DC GZVA cache lines until we have no more full cache lines.
"bic %[Next], %[End], LineMask \n\t"
".unreq LineMask \n\t"
"2: \n\t"
"dc gzva, %[Cur] \n\t"
"add %[Cur], %[Cur], %[LineSize] \n\t"
"cmp %[Cur], %[Next] \n\t"
"b.lt 2b \n\t"
// STZG until the end of the tagged region. This loop is also used to handle
// slow path cases.
"3: \n\t"
"cmp %[Cur], %[End] \n\t"
"b.ge 4f \n\t"
"stzg %[Cur], [%[Cur]], #16 \n\t"
"b 3b \n\t"
"4: \n\t"
: [Cur] "+&r"(Begin), [LineSize] "=&r"(LineSize), [Next] "=&r"(Next), [Tmp] "=&r"(Tmp)
: [End] "r"(End)
: "memory"
);
}
#endif

View file

@ -10,10 +10,7 @@ size_classes = [
1280, 1536, 1792, 2048, 1280, 1536, 1792, 2048,
2560, 3072, 3584, 4096, 2560, 3072, 3584, 4096,
5120, 6144, 7168, 8192, 5120, 6144, 7168, 8192,
10240, 12288, 14336, 16384, 10240, 12288, 14336, 16384
20480, 24576, 28672, 32768,
40960, 49152, 57344, 65536,
81920, 98304, 114688, 131072,
] ]
size_class_slots = [ size_class_slots = [
@ -24,10 +21,7 @@ size_class_slots = [
16, 16, 16, 16, 16, 16, 16, 16,
8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8,
6, 5, 4, 4, 6, 5, 4, 4
2, 2, 2, 2,
1, 1, 1, 1,
1, 1, 1, 1,
] ]
fragmentation = [100 - 1 / 16 * 100] fragmentation = [100 - 1 / 16 * 100]
@ -49,7 +43,7 @@ for size, slots, fragmentation in zip(size_classes, size_class_slots, fragmentat
used = size * slots used = size * slots
real = page_align(used) real = page_align(used)
print("| ", end='') print("| ", end='')
print(size, f"{fragmentation:.4}%", slots, real, str(100 - used / real * 100) + "%", sep=" | ", end=" |\n") print(size, str(fragmentation) + "%", slots, real, str(100 - used / real * 100) + "%", sep=" | ", end=" |\n")
if len(argv) < 2: if len(argv) < 2:
exit() exit()

View file

@ -4,9 +4,6 @@
#include "chacha.h" #include "chacha.h"
// ChaCha8
static const unsigned rounds = 8;
#define U8C(v) (v##U) #define U8C(v) (v##U)
#define U32C(v) (v##U) #define U32C(v) (v##U)
@ -41,7 +38,7 @@ static const unsigned rounds = 8;
a = PLUS(a, b); d = ROTATE(XOR(d, a), 8); \ a = PLUS(a, b); d = ROTATE(XOR(d, a), 8); \
c = PLUS(c, d); b = ROTATE(XOR(b, c), 7); c = PLUS(c, d); b = ROTATE(XOR(b, c), 7);
static const char sigma[16] NONSTRING = "expand 32-byte k"; static const char sigma[16] = "expand 32-byte k";
void chacha_keysetup(chacha_ctx *x, const u8 *k) { void chacha_keysetup(chacha_ctx *x, const u8 *k) {
x->input[0] = U8TO32_LITTLE(sigma + 0); x->input[0] = U8TO32_LITTLE(sigma + 0);
@ -66,52 +63,55 @@ void chacha_ivsetup(chacha_ctx *x, const u8 *iv) {
} }
void chacha_keystream_bytes(chacha_ctx *x, u8 *c, u32 bytes) { void chacha_keystream_bytes(chacha_ctx *x, u8 *c, u32 bytes) {
u32 x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
u32 j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
u8 *ctarget;
u8 tmp[64];
unsigned i;
if (!bytes) { if (!bytes) {
return; return;
} }
u8 *ctarget; j0 = x->input[0];
u8 tmp[64]; j1 = x->input[1];
j2 = x->input[2];
u32 j0 = x->input[0]; j3 = x->input[3];
u32 j1 = x->input[1]; j4 = x->input[4];
u32 j2 = x->input[2]; j5 = x->input[5];
u32 j3 = x->input[3]; j6 = x->input[6];
u32 j4 = x->input[4]; j7 = x->input[7];
u32 j5 = x->input[5]; j8 = x->input[8];
u32 j6 = x->input[6]; j9 = x->input[9];
u32 j7 = x->input[7]; j10 = x->input[10];
u32 j8 = x->input[8]; j11 = x->input[11];
u32 j9 = x->input[9]; j12 = x->input[12];
u32 j10 = x->input[10]; j13 = x->input[13];
u32 j11 = x->input[11]; j14 = x->input[14];
u32 j12 = x->input[12]; j15 = x->input[15];
u32 j13 = x->input[13];
u32 j14 = x->input[14];
u32 j15 = x->input[15];
for (;;) { for (;;) {
if (bytes < 64) { if (bytes < 64) {
ctarget = c; ctarget = c;
c = tmp; c = tmp;
} }
u32 x0 = j0; x0 = j0;
u32 x1 = j1; x1 = j1;
u32 x2 = j2; x2 = j2;
u32 x3 = j3; x3 = j3;
u32 x4 = j4; x4 = j4;
u32 x5 = j5; x5 = j5;
u32 x6 = j6; x6 = j6;
u32 x7 = j7; x7 = j7;
u32 x8 = j8; x8 = j8;
u32 x9 = j9; x9 = j9;
u32 x10 = j10; x10 = j10;
u32 x11 = j11; x11 = j11;
u32 x12 = j12; x12 = j12;
u32 x13 = j13; x13 = j13;
u32 x14 = j14; x14 = j14;
u32 x15 = j15; x15 = j15;
for (unsigned i = rounds; i > 0; i -= 2) { for (i = 8; i > 0; i -= 2) {
QUARTERROUND(x0, x4, x8, x12) QUARTERROUND(x0, x4, x8, x12)
QUARTERROUND(x1, x5, x9, x13) QUARTERROUND(x1, x5, x9, x13)
QUARTERROUND(x2, x6, x10, x14) QUARTERROUND(x2, x6, x10, x14)
@ -163,7 +163,7 @@ void chacha_keystream_bytes(chacha_ctx *x, u8 *c, u32 bytes) {
if (bytes <= 64) { if (bytes <= 64) {
if (bytes < 64) { if (bytes < 64) {
for (unsigned i = 0; i < bytes; ++i) { for (i = 0; i < bytes; ++i) {
ctarget[i] = c[i]; ctarget[i] = c[i];
} }
} }

View file

@ -1,23 +0,0 @@
CONFIG_WERROR := true
CONFIG_NATIVE := true
CONFIG_CXX_ALLOCATOR := true
CONFIG_UBSAN := false
CONFIG_SEAL_METADATA := false
CONFIG_ZERO_ON_FREE := true
CONFIG_WRITE_AFTER_FREE_CHECK := true
CONFIG_SLOT_RANDOMIZE := true
CONFIG_SLAB_CANARY := true
CONFIG_SLAB_QUARANTINE_RANDOM_LENGTH := 1
CONFIG_SLAB_QUARANTINE_QUEUE_LENGTH := 1
CONFIG_EXTENDED_SIZE_CLASSES := true
CONFIG_LARGE_SIZE_CLASSES := true
CONFIG_GUARD_SLABS_INTERVAL := 1
CONFIG_GUARD_SIZE_DIVISOR := 2
CONFIG_REGION_QUARANTINE_RANDOM_LENGTH := 256
CONFIG_REGION_QUARANTINE_QUEUE_LENGTH := 1024
CONFIG_REGION_QUARANTINE_SKIP_THRESHOLD := 33554432 # 32MiB
CONFIG_FREE_SLABS_QUARANTINE_RANDOM_LENGTH := 32
CONFIG_CLASS_REGION_SIZE := 34359738368 # 32GiB
CONFIG_N_ARENA := 4
CONFIG_STATS := false
CONFIG_SELF_INIT := true

View file

@ -1,23 +0,0 @@
CONFIG_WERROR := true
CONFIG_NATIVE := true
CONFIG_CXX_ALLOCATOR := true
CONFIG_UBSAN := false
CONFIG_SEAL_METADATA := false
CONFIG_ZERO_ON_FREE := true
CONFIG_WRITE_AFTER_FREE_CHECK := false
CONFIG_SLOT_RANDOMIZE := false
CONFIG_SLAB_CANARY := true
CONFIG_SLAB_QUARANTINE_RANDOM_LENGTH := 0
CONFIG_SLAB_QUARANTINE_QUEUE_LENGTH := 0
CONFIG_EXTENDED_SIZE_CLASSES := true
CONFIG_LARGE_SIZE_CLASSES := true
CONFIG_GUARD_SLABS_INTERVAL := 8
CONFIG_GUARD_SIZE_DIVISOR := 2
CONFIG_REGION_QUARANTINE_RANDOM_LENGTH := 256
CONFIG_REGION_QUARANTINE_QUEUE_LENGTH := 1024
CONFIG_REGION_QUARANTINE_SKIP_THRESHOLD := 33554432 # 32MiB
CONFIG_FREE_SLABS_QUARANTINE_RANDOM_LENGTH := 32
CONFIG_CLASS_REGION_SIZE := 34359738368 # 32GiB
CONFIG_N_ARENA := 4
CONFIG_STATS := false
CONFIG_SELF_INIT := true

1534
h_malloc.c

File diff suppressed because it is too large Load diff

View file

@ -5,9 +5,7 @@
#include <malloc.h> #include <malloc.h>
#ifdef __cplusplus __BEGIN_DECLS
extern "C" {
#endif
#ifndef H_MALLOC_PREFIX #ifndef H_MALLOC_PREFIX
#define h_malloc malloc #define h_malloc malloc
@ -23,7 +21,6 @@ extern "C" {
#define h_malloc_trim malloc_trim #define h_malloc_trim malloc_trim
#define h_malloc_stats malloc_stats #define h_malloc_stats malloc_stats
#define h_mallinfo mallinfo #define h_mallinfo mallinfo
#define h_mallinfo2 mallinfo2
#define h_malloc_info malloc_info #define h_malloc_info malloc_info
#define h_memalign memalign #define h_memalign memalign
@ -33,12 +30,7 @@ extern "C" {
#define h_malloc_get_state malloc_get_state #define h_malloc_get_state malloc_get_state
#define h_malloc_set_state malloc_set_state #define h_malloc_set_state malloc_set_state
#define h_mallinfo_narenas mallinfo_narenas #define h_iterate iterate
#define h_mallinfo_nbins mallinfo_nbins
#define h_mallinfo_arena_info mallinfo_arena_info
#define h_mallinfo_bin_info mallinfo_bin_info
#define h_malloc_iterate malloc_iterate
#define h_malloc_disable malloc_disable #define h_malloc_disable malloc_disable
#define h_malloc_enable malloc_enable #define h_malloc_enable malloc_enable
@ -48,10 +40,9 @@ extern "C" {
#endif #endif
// C standard // C standard
__attribute__((malloc)) __attribute__((alloc_size(1))) void *h_malloc(size_t size); void *h_malloc(size_t size);
__attribute__((malloc)) __attribute__((alloc_size(1, 2))) void *h_calloc(size_t nmemb, size_t size); void *h_calloc(size_t nmemb, size_t size);
__attribute__((alloc_size(2))) void *h_realloc(void *ptr, size_t size); void *h_realloc(void *ptr, size_t size);
__attribute__((malloc)) __attribute__((alloc_size(2))) __attribute__((alloc_align(1)))
void *h_aligned_alloc(size_t alignment, size_t size); void *h_aligned_alloc(size_t alignment, size_t size);
void h_free(void *ptr); void h_free(void *ptr);
@ -72,43 +63,35 @@ void h_malloc_stats(void);
#if defined(__GLIBC__) || defined(__ANDROID__) #if defined(__GLIBC__) || defined(__ANDROID__)
struct mallinfo h_mallinfo(void); struct mallinfo h_mallinfo(void);
#endif #endif
#ifndef __ANDROID__
int h_malloc_info(int options, FILE *fp); int h_malloc_info(int options, FILE *fp);
#endif
// obsolete glibc extensions // obsolete glibc extensions
__attribute__((malloc)) __attribute__((alloc_size(2))) __attribute__((alloc_align(1)))
void *h_memalign(size_t alignment, size_t size); void *h_memalign(size_t alignment, size_t size);
#ifndef __ANDROID__ void *h_valloc(size_t size);
__attribute__((malloc)) __attribute__((alloc_size(1))) void *h_valloc(size_t size); void *h_pvalloc(size_t size);
__attribute__((malloc)) void *h_pvalloc(size_t size); void h_cfree(void *ptr);
#endif
#ifdef __GLIBC__
void h_cfree(void *ptr) __THROW;
void *h_malloc_get_state(void); void *h_malloc_get_state(void);
int h_malloc_set_state(void *state); int h_malloc_set_state(void *state);
#endif
// Android extensions // Android extensions
#ifdef __ANDROID__ #ifdef __ANDROID__
size_t h_mallinfo_narenas(void); size_t __mallinfo_narenas(void);
size_t h_mallinfo_nbins(void); size_t __mallinfo_nbins(void);
struct mallinfo h_mallinfo_arena_info(size_t arena); struct mallinfo __mallinfo_arena_info(size_t arena);
struct mallinfo h_mallinfo_bin_info(size_t arena, size_t bin); struct mallinfo __mallinfo_bin_info(size_t arena, size_t bin);
int h_malloc_iterate(uintptr_t base, size_t size, void (*callback)(uintptr_t ptr, size_t size, void *arg), int h_iterate(uintptr_t base, size_t size, void (*callback)(uintptr_t ptr, size_t size, void *arg),
void *arg); void *arg);
void h_malloc_disable(void); void h_malloc_disable(void);
void h_malloc_enable(void); void h_malloc_enable(void);
void h_malloc_disable_memory_tagging(void);
#endif #endif
// hardened_malloc extensions // custom extensions
// return an upper bound on object size for any pointer based on malloc metadata // return an upper bound on object size for any pointer based on malloc metadata
size_t h_malloc_object_size(const void *ptr); size_t h_malloc_object_size(void *ptr);
// similar to malloc_object_size, but avoiding locking so the results are much more limited // similar to malloc_object_size, but avoiding locking so the results are much more limited
size_t h_malloc_object_size_fast(const void *ptr); size_t h_malloc_object_size_fast(void *ptr);
// The free function with an extra parameter for passing the size requested at // The free function with an extra parameter for passing the size requested at
// allocation time. // allocation time.
@ -122,8 +105,6 @@ size_t h_malloc_object_size_fast(const void *ptr);
// passed size matches the allocated size. // passed size matches the allocated size.
void h_free_sized(void *ptr, size_t expected_size); void h_free_sized(void *ptr, size_t expected_size);
#ifdef __cplusplus __END_DECLS
}
#endif
#endif #endif

View file

@ -1,10 +1,7 @@
#include <errno.h> #include <errno.h>
#include <sys/mman.h> #include <sys/mman.h>
#ifdef LABEL_MEMORY
#include <sys/prctl.h> #include <sys/prctl.h>
#endif
#ifndef PR_SET_VMA #ifndef PR_SET_VMA
#define PR_SET_VMA 0x53564d41 #define PR_SET_VMA 0x53564d41
@ -17,8 +14,8 @@
#include "memory.h" #include "memory.h"
#include "util.h" #include "util.h"
static void *memory_map_prot(size_t size, int prot) { void *memory_map(size_t size) {
void *p = mmap(NULL, size, prot, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); void *p = mmap(NULL, size, PROT_NONE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
if (unlikely(p == MAP_FAILED)) { if (unlikely(p == MAP_FAILED)) {
if (errno != ENOMEM) { if (errno != ENOMEM) {
fatal_error("non-ENOMEM mmap failure"); fatal_error("non-ENOMEM mmap failure");
@ -28,50 +25,30 @@ static void *memory_map_prot(size_t size, int prot) {
return p; return p;
} }
void *memory_map(size_t size) { int memory_map_fixed(void *ptr, size_t size) {
return memory_map_prot(size, PROT_NONE); void *p = mmap(ptr, size, PROT_NONE, MAP_ANONYMOUS|MAP_PRIVATE|MAP_FIXED, -1, 0);
} if (unlikely(p == MAP_FAILED)) {
if (errno != ENOMEM) {
#ifdef HAS_ARM_MTE fatal_error("non-ENOMEM MAP_FIXED mmap failure");
// Note that PROT_MTE can't be cleared via mprotect }
void *memory_map_mte(size_t size) { return 1;
return memory_map_prot(size, PROT_MTE);
}
#endif
static bool memory_map_fixed_prot(void *ptr, size_t size, int prot) {
void *p = mmap(ptr, size, prot, MAP_ANONYMOUS|MAP_PRIVATE|MAP_FIXED, -1, 0);
bool ret = p == MAP_FAILED;
if (unlikely(ret) && errno != ENOMEM) {
fatal_error("non-ENOMEM MAP_FIXED mmap failure");
} }
return ret; return 0;
} }
bool memory_map_fixed(void *ptr, size_t size) { int memory_unmap(void *ptr, size_t size) {
return memory_map_fixed_prot(ptr, size, PROT_NONE); int ret = munmap(ptr, size);
}
#ifdef HAS_ARM_MTE
// Note that PROT_MTE can't be cleared via mprotect
bool memory_map_fixed_mte(void *ptr, size_t size) {
return memory_map_fixed_prot(ptr, size, PROT_MTE);
}
#endif
bool memory_unmap(void *ptr, size_t size) {
bool ret = munmap(ptr, size);
if (unlikely(ret) && errno != ENOMEM) { if (unlikely(ret) && errno != ENOMEM) {
fatal_error("non-ENOMEM munmap failure"); fatal_error("non-ENOMEM munmap failure");
} }
return ret; return ret;
} }
static bool memory_protect_prot(void *ptr, size_t size, int prot, UNUSED int pkey) { static int memory_protect_prot(void *ptr, size_t size, int prot, UNUSED int pkey) {
#ifdef USE_PKEY #ifdef USE_PKEY
bool ret = pkey_mprotect(ptr, size, prot, pkey); int ret = pkey_mprotect(ptr, size, prot, pkey);
#else #else
bool ret = mprotect(ptr, size, prot); int ret = mprotect(ptr, size, prot);
#endif #endif
if (unlikely(ret) && errno != ENOMEM) { if (unlikely(ret) && errno != ENOMEM) {
fatal_error("non-ENOMEM mprotect failure"); fatal_error("non-ENOMEM mprotect failure");
@ -79,50 +56,42 @@ static bool memory_protect_prot(void *ptr, size_t size, int prot, UNUSED int pke
return ret; return ret;
} }
bool memory_protect_ro(void *ptr, size_t size) { int memory_protect_ro(void *ptr, size_t size) {
return memory_protect_prot(ptr, size, PROT_READ, -1); return memory_protect_prot(ptr, size, PROT_READ, -1);
} }
bool memory_protect_rw(void *ptr, size_t size) { int memory_protect_rw(void *ptr, size_t size) {
return memory_protect_prot(ptr, size, PROT_READ|PROT_WRITE, -1); return memory_protect_prot(ptr, size, PROT_READ|PROT_WRITE, -1);
} }
bool memory_protect_rw_metadata(void *ptr, size_t size) { int memory_protect_rw_metadata(void *ptr, size_t size) {
return memory_protect_prot(ptr, size, PROT_READ|PROT_WRITE, get_metadata_key()); return memory_protect_prot(ptr, size, PROT_READ|PROT_WRITE, get_metadata_key());
} }
#ifdef HAVE_COMPATIBLE_MREMAP int memory_remap(void *old, size_t old_size, size_t new_size) {
bool memory_remap(void *old, size_t old_size, size_t new_size) {
void *ptr = mremap(old, old_size, new_size, 0); void *ptr = mremap(old, old_size, new_size, 0);
bool ret = ptr == MAP_FAILED; if (unlikely(ptr == MAP_FAILED)) {
if (unlikely(ret) && errno != ENOMEM) { if (errno != ENOMEM) {
fatal_error("non-ENOMEM mremap failure"); fatal_error("non-ENOMEM mremap failure");
}
return 1;
} }
return ret; return 0;
} }
bool memory_remap_fixed(void *old, size_t old_size, void *new, size_t new_size) { int memory_remap_fixed(void *old, size_t old_size, void *new, size_t new_size) {
void *ptr = mremap(old, old_size, new_size, MREMAP_MAYMOVE|MREMAP_FIXED, new); void *ptr = mremap(old, old_size, new_size, MREMAP_MAYMOVE|MREMAP_FIXED, new);
bool ret = ptr == MAP_FAILED; if (unlikely(ptr == MAP_FAILED)) {
if (unlikely(ret) && errno != ENOMEM) { if (errno != ENOMEM) {
fatal_error("non-ENOMEM MREMAP_FIXED mremap failure"); fatal_error("non-ENOMEM MREMAP_FIXED mremap failure");
}
return 1;
} }
return ret; return 0;
}
#endif
bool memory_purge(void *ptr, size_t size) {
int ret = madvise(ptr, size, MADV_DONTNEED);
if (unlikely(ret) && errno != ENOMEM) {
fatal_error("non-ENOMEM MADV_DONTNEED madvise failure");
}
return ret;
} }
bool memory_set_name(UNUSED void *ptr, UNUSED size_t size, UNUSED const char *name) { void memory_set_name(UNUSED void *ptr, UNUSED size_t size, UNUSED const char *name) {
#ifdef LABEL_MEMORY #ifdef LABEL_MEMORY
return prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, ptr, size, name); prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, ptr, size, name);
#else
return false;
#endif #endif
} }

View file

@ -1,32 +1,18 @@
#ifndef MEMORY_H #ifndef MEMORY_H
#define MEMORY_H #define MEMORY_H
#include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#ifdef __linux__
#define HAVE_COMPATIBLE_MREMAP
#endif
int get_metadata_key(void); int get_metadata_key(void);
void *memory_map(size_t size); void *memory_map(size_t size);
#ifdef HAS_ARM_MTE int memory_map_fixed(void *ptr, size_t size);
void *memory_map_mte(size_t size); int memory_unmap(void *ptr, size_t size);
#endif int memory_protect_ro(void *ptr, size_t size);
bool memory_map_fixed(void *ptr, size_t size); int memory_protect_rw(void *ptr, size_t size);
#ifdef HAS_ARM_MTE int memory_protect_rw_metadata(void *ptr, size_t size);
bool memory_map_fixed_mte(void *ptr, size_t size); int memory_remap(void *old, size_t old_size, size_t new_size);
#endif int memory_remap_fixed(void *old, size_t old_size, void *new, size_t new_size);
bool memory_unmap(void *ptr, size_t size); void memory_set_name(void *ptr, size_t size, const char *name);
bool memory_protect_ro(void *ptr, size_t size);
bool memory_protect_rw(void *ptr, size_t size);
bool memory_protect_rw_metadata(void *ptr, size_t size);
#ifdef HAVE_COMPATIBLE_MREMAP
bool memory_remap(void *old, size_t old_size, size_t new_size);
bool memory_remap_fixed(void *old, size_t old_size, void *new, size_t new_size);
#endif
bool memory_purge(void *ptr, size_t size);
bool memory_set_name(void *ptr, size_t size, const char *name);
#endif #endif

View file

@ -1,50 +0,0 @@
#ifndef MEMTAG_H
#define MEMTAG_H
#include "util.h"
#ifdef HAS_ARM_MTE
#include "arm_mte.h"
#define MEMTAG 1
// Note that bionic libc always reserves tag 0 via PR_MTE_TAG_MASK prctl
#define RESERVED_TAG 0
#define TAG_WIDTH 4
#endif
static inline void *untag_pointer(void *ptr) {
#ifdef HAS_ARM_MTE
const uintptr_t mask = UINTPTR_MAX >> 8;
return (void *) ((uintptr_t) ptr & mask);
#else
return ptr;
#endif
}
static inline const void *untag_const_pointer(const void *ptr) {
#ifdef HAS_ARM_MTE
const uintptr_t mask = UINTPTR_MAX >> 8;
return (const void *) ((uintptr_t) ptr & mask);
#else
return ptr;
#endif
}
static inline void *set_pointer_tag(void *ptr, u8 tag) {
#ifdef HAS_ARM_MTE
return (void *) (((uintptr_t) tag << 56) | (uintptr_t) untag_pointer(ptr));
#else
(void) tag;
return ptr;
#endif
}
static inline u8 get_pointer_tag(void *ptr) {
#ifdef HAS_ARM_MTE
return (((uintptr_t) ptr) >> 56) & 0xf;
#else
(void) ptr;
return 0;
#endif
}
#endif

8
new.cc
View file

@ -1,10 +1,8 @@
// needed with libstdc++ but not libc++
#if __has_include(<bits/functexcept.h>)
#include <bits/functexcept.h> #include <bits/functexcept.h>
#endif
#include <new> #include <new>
#define noreturn
#include "h_malloc.h" #include "h_malloc.h"
#include "util.h" #include "util.h"
@ -80,6 +78,7 @@ EXPORT void operator delete[](void *ptr, size_t size) noexcept {
h_free_sized(ptr, size); h_free_sized(ptr, size);
} }
#if __cplusplus >= 201703L
COLD static void *handle_out_of_memory(size_t size, size_t alignment, bool nothrow) { COLD static void *handle_out_of_memory(size_t size, size_t alignment, bool nothrow) {
void *ptr = nullptr; void *ptr = nullptr;
@ -151,3 +150,4 @@ EXPORT void operator delete(void *ptr, size_t size, std::align_val_t) noexcept {
EXPORT void operator delete[](void *ptr, size_t size, std::align_val_t) noexcept { EXPORT void operator delete[](void *ptr, size_t size, std::align_val_t) noexcept {
h_free_sized(ptr, size); h_free_sized(ptr, size);
} }
#endif

17
pages.c
View file

@ -4,14 +4,13 @@
#include "pages.h" #include "pages.h"
#include "util.h" #include "util.h"
static bool add_guards(size_t size, size_t guard_size, size_t *total_size) { static uintptr_t alignment_ceiling(uintptr_t s, uintptr_t alignment) {
return __builtin_add_overflow(size, guard_size, total_size) || return ((s) + (alignment - 1)) & ((~alignment) + 1);
__builtin_add_overflow(*total_size, guard_size, total_size);
} }
void *allocate_pages(size_t usable_size, size_t guard_size, bool unprotect, const char *name) { void *allocate_pages(size_t usable_size, size_t guard_size, bool unprotect, const char *name) {
size_t real_size; size_t real_size;
if (unlikely(add_guards(usable_size, guard_size, &real_size))) { if (unlikely(__builtin_add_overflow(usable_size, guard_size * 2, &real_size))) {
errno = ENOMEM; errno = ENOMEM;
return NULL; return NULL;
} }
@ -29,7 +28,7 @@ void *allocate_pages(size_t usable_size, size_t guard_size, bool unprotect, cons
} }
void *allocate_pages_aligned(size_t usable_size, size_t alignment, size_t guard_size, const char *name) { void *allocate_pages_aligned(size_t usable_size, size_t alignment, size_t guard_size, const char *name) {
usable_size = page_align(usable_size); usable_size = PAGE_CEILING(usable_size);
if (unlikely(!usable_size)) { if (unlikely(!usable_size)) {
errno = ENOMEM; errno = ENOMEM;
return NULL; return NULL;
@ -42,7 +41,7 @@ void *allocate_pages_aligned(size_t usable_size, size_t alignment, size_t guard_
} }
size_t real_alloc_size; size_t real_alloc_size;
if (unlikely(add_guards(alloc_size, guard_size, &real_alloc_size))) { if (unlikely(__builtin_add_overflow(alloc_size, guard_size * 2, &real_alloc_size))) {
errno = ENOMEM; errno = ENOMEM;
return NULL; return NULL;
} }
@ -55,7 +54,7 @@ void *allocate_pages_aligned(size_t usable_size, size_t alignment, size_t guard_
void *usable = (char *)real + guard_size; void *usable = (char *)real + guard_size;
size_t lead_size = align((uintptr_t)usable, alignment) - (uintptr_t)usable; size_t lead_size = alignment_ceiling((uintptr_t)usable, alignment) - (uintptr_t)usable;
size_t trail_size = alloc_size - lead_size - usable_size; size_t trail_size = alloc_size - lead_size - usable_size;
void *base = (char *)usable + lead_size; void *base = (char *)usable + lead_size;
@ -82,7 +81,5 @@ void *allocate_pages_aligned(size_t usable_size, size_t alignment, size_t guard_
} }
void deallocate_pages(void *usable, size_t usable_size, size_t guard_size) { void deallocate_pages(void *usable, size_t usable_size, size_t guard_size) {
if (unlikely(memory_unmap((char *)usable - guard_size, usable_size + guard_size * 2))) { memory_unmap((char *)usable - guard_size, usable_size + guard_size * 2);
memory_purge(usable, usable_size);
}
} }

View file

@ -5,21 +5,16 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include "util.h"
#define PAGE_SHIFT 12 #define PAGE_SHIFT 12
#ifndef PAGE_SIZE #ifndef PAGE_SIZE
#define PAGE_SIZE ((size_t)1 << PAGE_SHIFT) #define PAGE_SIZE ((size_t)1 << PAGE_SHIFT)
#endif #endif
#define PAGE_CEILING(s) (((s) + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1))
void *allocate_pages(size_t usable_size, size_t guard_size, bool unprotect, const char *name); void *allocate_pages(size_t usable_size, size_t guard_size, bool unprotect, const char *name);
void *allocate_pages_aligned(size_t usable_size, size_t alignment, size_t guard_size, const char *name); void *allocate_pages_aligned(size_t usable_size, size_t alignment, size_t guard_size, const char *name);
void deallocate_pages(void *usable, size_t usable_size, size_t guard_size); void deallocate_pages(void *usable, size_t usable_size, size_t guard_size);
static inline size_t page_align(size_t size) {
return align(size, PAGE_SIZE);
}
static inline size_t hash_page(const void *p) { static inline size_t hash_page(const void *p) {
uintptr_t u = (uintptr_t)p >> PAGE_SHIFT; uintptr_t u = (uintptr_t)p >> PAGE_SHIFT;
size_t sum = u; size_t sum = u;

View file

@ -5,10 +5,20 @@
#include "random.h" #include "random.h"
#include "util.h" #include "util.h"
#if __has_include(<sys/random.h>)
// glibc 2.25 and later
#include <sys/random.h> #include <sys/random.h>
#else
#include <unistd.h>
#include <sys/syscall.h>
static ssize_t getrandom(void *buf, size_t buflen, unsigned int flags) {
return syscall(SYS_getrandom, buf, buflen, flags);
}
#endif
static void get_random_seed(void *buf, size_t size) { static void get_random_seed(void *buf, size_t size) {
while (size) { while (size > 0) {
ssize_t r; ssize_t r;
do { do {
@ -29,48 +39,19 @@ void random_state_init(struct random_state *state) {
get_random_seed(rnd, sizeof(rnd)); get_random_seed(rnd, sizeof(rnd));
chacha_keysetup(&state->ctx, rnd); chacha_keysetup(&state->ctx, rnd);
chacha_ivsetup(&state->ctx, rnd + CHACHA_KEY_SIZE); chacha_ivsetup(&state->ctx, rnd + CHACHA_KEY_SIZE);
state->index = RANDOM_CACHE_SIZE; chacha_keystream_bytes(&state->ctx, state->cache, RANDOM_CACHE_SIZE);
state->reseed = 0; state->index = 0;
}
void random_state_init_from_random_state(struct random_state *state, struct random_state *source) {
u8 rnd[CHACHA_KEY_SIZE + CHACHA_IV_SIZE];
get_random_bytes(source, rnd, sizeof(rnd));
chacha_keysetup(&state->ctx, rnd);
chacha_ivsetup(&state->ctx, rnd + CHACHA_KEY_SIZE);
state->index = RANDOM_CACHE_SIZE;
state->reseed = 0; state->reseed = 0;
} }
static void refill(struct random_state *state) { static void refill(struct random_state *state) {
if (state->reseed >= RANDOM_RESEED_SIZE) { if (state->reseed < RANDOM_RESEED_SIZE) {
chacha_keystream_bytes(&state->ctx, state->cache, RANDOM_CACHE_SIZE);
state->index = 0;
state->reseed += RANDOM_CACHE_SIZE;
} else {
random_state_init(state); random_state_init(state);
} }
chacha_keystream_bytes(&state->ctx, state->cache, RANDOM_CACHE_SIZE);
state->index = 0;
state->reseed += RANDOM_CACHE_SIZE;
}
void get_random_bytes(struct random_state *state, void *buf, size_t size) {
// avoid needless copying to and from the cache as an optimization
if (size > RANDOM_CACHE_SIZE / 2) {
chacha_keystream_bytes(&state->ctx, buf, size);
return;
}
while (size) {
if (state->index == RANDOM_CACHE_SIZE) {
refill(state);
}
size_t remaining = RANDOM_CACHE_SIZE - state->index;
size_t copy_size = min(size, remaining);
memcpy(buf, state->cache + state->index, copy_size);
state->index += copy_size;
buf = (char *)buf + copy_size;
size -= copy_size;
}
} }
u16 get_random_u16(struct random_state *state) { u16 get_random_u16(struct random_state *state) {
@ -92,7 +73,7 @@ u16 get_random_u16_uniform(struct random_state *state, u16 bound) {
if (leftover < bound) { if (leftover < bound) {
u16 threshold = -bound % bound; u16 threshold = -bound % bound;
while (leftover < threshold) { while (leftover < threshold) {
random = get_random_u16(state); random = get_random_u16(state);
multiresult = random * bound; multiresult = random * bound;
leftover = (u16)multiresult; leftover = (u16)multiresult;
} }
@ -119,7 +100,7 @@ u64 get_random_u64_uniform(struct random_state *state, u64 bound) {
if (leftover < bound) { if (leftover < bound) {
u64 threshold = -bound % bound; u64 threshold = -bound % bound;
while (leftover < threshold) { while (leftover < threshold) {
random = get_random_u64(state); random = get_random_u64(state);
multiresult = random * bound; multiresult = random * bound;
leftover = multiresult; leftover = multiresult;
} }

View file

@ -15,8 +15,6 @@ struct random_state {
}; };
void random_state_init(struct random_state *state); void random_state_init(struct random_state *state);
void random_state_init_from_random_state(struct random_state *state, struct random_state *source);
void get_random_bytes(struct random_state *state, void *buf, size_t size);
u16 get_random_u16(struct random_state *state); u16 get_random_u16(struct random_state *state);
u16 get_random_u16_uniform(struct random_state *state, u16 bound); u16 get_random_u16_uniform(struct random_state *state, u16 bound);
u64 get_random_u64(struct random_state *state); u64 get_random_u64(struct random_state *state);

43
test/.gitignore vendored
View file

@ -1,44 +1 @@
large_array_growth
mallinfo
mallinfo2
malloc_info
offset offset
delete_type_size_mismatch
double_free_large
double_free_large_delayed
double_free_small
double_free_small_delayed
invalid_free_protected
invalid_free_small_region
invalid_free_small_region_far
invalid_free_unprotected
read_after_free_large
read_after_free_small
read_zero_size
string_overflow
unaligned_free_large
unaligned_free_small
uninitialized_free
uninitialized_malloc_usable_size
uninitialized_realloc
write_after_free_large
write_after_free_large_reuse
write_after_free_small
write_after_free_small_reuse
write_zero_size
unaligned_malloc_usable_size_small
invalid_malloc_usable_size_small
invalid_malloc_usable_size_small_quarantine
malloc_object_size
malloc_object_size_offset
invalid_malloc_object_size_small
invalid_malloc_object_size_small_quarantine
impossibly_large_malloc
overflow_large_1_byte
overflow_large_8_byte
overflow_small_1_byte
overflow_small_8_byte
uninitialized_read_large
uninitialized_read_small
realloc_init
__pycache__/

View file

@ -1,76 +1,16 @@
CONFIG_SLAB_CANARY := true CONFIG_SLAB_CANARY := true
CONFIG_EXTENDED_SIZE_CLASSES := true
ifneq ($(VARIANT),)
$(error testing non-default variants not yet supported)
endif
ifeq (,$(filter $(CONFIG_SLAB_CANARY),true false)) ifeq (,$(filter $(CONFIG_SLAB_CANARY),true false))
$(error CONFIG_SLAB_CANARY must be true or false) $(error CONFIG_SLAB_CANARY must be true or false)
endif endif
dir=$(dir $(realpath $(firstword $(MAKEFILE_LIST)))) CPPFLAGS += \
-DSLAB_CANARY=$(CONFIG_SLAB_CANARY)
CPPFLAGS := \
-D_GNU_SOURCE \
-DSLAB_CANARY=$(CONFIG_SLAB_CANARY) \
-DCONFIG_EXTENDED_SIZE_CLASSES=$(CONFIG_EXTENDED_SIZE_CLASSES)
SHARED_FLAGS := -O3
CFLAGS := -std=c17 $(SHARED_FLAGS) -Wmissing-prototypes
CXXFLAGS := -std=c++17 -fsized-deallocation $(SHARED_FLAGS)
LDFLAGS := -Wl,-L$(dir)../out,-R,$(dir)../out
LDLIBS := -lpthread -lhardened_malloc
EXECUTABLES := \ EXECUTABLES := \
offset \ offset
mallinfo \
mallinfo2 \
malloc_info \
large_array_growth \
double_free_large \
double_free_large_delayed \
double_free_small \
double_free_small_delayed \
unaligned_free_large \
unaligned_free_small \
read_after_free_large \
read_after_free_small \
write_after_free_large \
write_after_free_large_reuse \
write_after_free_small \
write_after_free_small_reuse \
read_zero_size \
write_zero_size \
invalid_free_protected \
invalid_free_unprotected \
invalid_free_small_region \
invalid_free_small_region_far \
uninitialized_read_small \
uninitialized_read_large \
uninitialized_free \
uninitialized_realloc \
uninitialized_malloc_usable_size \
overflow_large_1_byte \
overflow_large_8_byte \
overflow_small_1_byte \
overflow_small_8_byte \
string_overflow \
delete_type_size_mismatch \
unaligned_malloc_usable_size_small \
invalid_malloc_usable_size_small \
invalid_malloc_usable_size_small_quarantine \
malloc_object_size \
malloc_object_size_offset \
invalid_malloc_object_size_small \
invalid_malloc_object_size_small_quarantine \
impossibly_large_malloc \
realloc_init
all: $(EXECUTABLES) all: $(EXECUTABLES)
clean: clean:
rm -f $(EXECUTABLES) rm -f $(EXECUTABLES)
rm -fr ./__pycache__

View file

View file

@ -1,8 +0,0 @@
#include <stdlib.h>
#include "test_util.h"
OPTNONE int main(void) {
char *p = malloc(-8);
return !(p == NULL);
}

View file

@ -1,15 +0,0 @@
#include <stdlib.h>
#include "test_util.h"
size_t malloc_object_size(void *ptr);
OPTNONE int main(void) {
char *p = malloc(16);
if (!p) {
return 1;
}
char *q = p + 4096 * 4;
malloc_object_size(q);
return 0;
}

View file

@ -1,15 +0,0 @@
#include <stdlib.h>
#include "test_util.h"
size_t malloc_object_size(void *ptr);
OPTNONE int main(void) {
void *p = malloc(16);
if (!p) {
return 1;
}
free(p);
malloc_object_size(p);
return 0;
}

View file

@ -1,13 +0,0 @@
#include <malloc.h>
#include "test_util.h"
OPTNONE int main(void) {
char *p = malloc(16);
if (!p) {
return 1;
}
char *q = p + 4096 * 4;
malloc_usable_size(q);
return 0;
}

View file

@ -1,13 +0,0 @@
#include <malloc.h>
#include "test_util.h"
OPTNONE int main(void) {
void *p = malloc(16);
if (!p) {
return 1;
}
free(p);
malloc_usable_size(p);
return 0;
}

View file

@ -1,18 +0,0 @@
#include <stdlib.h>
#include <string.h>
#include "test_util.h"
OPTNONE int main(void) {
void *p = NULL;
size_t size = 256 * 1024;
for (unsigned i = 0; i < 20; i++) {
p = realloc(p, size);
if (!p) {
return 1;
}
memset(p, 'a', size);
size = size * 3 / 2;
}
}

View file

@ -1,44 +0,0 @@
#include <stdlib.h>
#include <stdio.h>
#if defined(__GLIBC__) || defined(__ANDROID__)
#include <malloc.h>
#endif
#include "test_util.h"
static void print_mallinfo(void) {
#if defined(__GLIBC__) || defined(__ANDROID__)
struct mallinfo info = mallinfo();
printf("mallinfo:\n");
printf("arena: %zu\n", (size_t)info.arena);
printf("ordblks: %zu\n", (size_t)info.ordblks);
printf("smblks: %zu\n", (size_t)info.smblks);
printf("hblks: %zu\n", (size_t)info.hblks);
printf("hblkhd: %zu\n", (size_t)info.hblkhd);
printf("usmblks: %zu\n", (size_t)info.usmblks);
printf("fsmblks: %zu\n", (size_t)info.fsmblks);
printf("uordblks: %zu\n", (size_t)info.uordblks);
printf("fordblks: %zu\n", (size_t)info.fordblks);
printf("keepcost: %zu\n", (size_t)info.keepcost);
#endif
}
OPTNONE int main(void) {
void *a[4];
a[0] = malloc(1024 * 1024 * 1024);
a[1] = malloc(16);
a[2] = malloc(32);
a[3] = malloc(64);
print_mallinfo();
free(a[0]);
free(a[1]);
free(a[2]);
free(a[3]);
printf("\n");
print_mallinfo();
}

View file

@ -1,44 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#if defined(__GLIBC__)
#include <malloc.h>
#endif
#include "test_util.h"
static void print_mallinfo2(void) {
#if defined(__GLIBC__)
struct mallinfo2 info = mallinfo2();
printf("mallinfo2:\n");
printf("arena: %zu\n", (size_t)info.arena);
printf("ordblks: %zu\n", (size_t)info.ordblks);
printf("smblks: %zu\n", (size_t)info.smblks);
printf("hblks: %zu\n", (size_t)info.hblks);
printf("hblkhd: %zu\n", (size_t)info.hblkhd);
printf("usmblks: %zu\n", (size_t)info.usmblks);
printf("fsmblks: %zu\n", (size_t)info.fsmblks);
printf("uordblks: %zu\n", (size_t)info.uordblks);
printf("fordblks: %zu\n", (size_t)info.fordblks);
printf("keepcost: %zu\n", (size_t)info.keepcost);
#endif
}
OPTNONE int main(void) {
void *a[4];
a[0] = malloc(1024 * 1024 * 1024);
a[1] = malloc(16);
a[2] = malloc(32);
a[3] = malloc(64);
print_mallinfo2();
free(a[0]);
free(a[1]);
free(a[2]);
free(a[3]);
printf("\n");
print_mallinfo2();
}

View file

@ -1,36 +0,0 @@
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#if defined(__GLIBC__) || defined(__ANDROID__)
#include <malloc.h>
#endif
#include "test_util.h"
#include "../util.h"
OPTNONE static void leak_memory(void) {
(void)!malloc(1024 * 1024 * 1024);
(void)!malloc(16);
(void)!malloc(32);
(void)!malloc(4096);
}
static void *do_work(UNUSED void *p) {
leak_memory();
return NULL;
}
int main(void) {
pthread_t thread[4];
for (int i = 0; i < 4; i++) {
pthread_create(&thread[i], NULL, do_work, NULL);
}
for (int i = 0; i < 4; i++) {
pthread_join(thread[i], NULL);
}
#if defined(__GLIBC__) || defined(__ANDROID__)
malloc_info(0, stdout);
#endif
}

View file

@ -1,12 +0,0 @@
#include <stdbool.h>
#include <stdlib.h>
#include "test_util.h"
size_t malloc_object_size(void *ptr);
OPTNONE int main(void) {
char *p = malloc(16);
size_t size = malloc_object_size(p);
return size != (SLAB_CANARY ? 24 : 32);
}

View file

@ -1,12 +0,0 @@
#include <stdbool.h>
#include <stdlib.h>
#include "test_util.h"
size_t malloc_object_size(void *ptr);
OPTNONE int main(void) {
char *p = malloc(16);
size_t size = malloc_object_size(p + 5);
return size != (SLAB_CANARY ? 19 : 27);
}

View file

@ -3,7 +3,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
static size_t size_classes[] = { static unsigned size_classes[] = {
/* large */ 4 * 1024 * 1024, /* large */ 4 * 1024 * 1024,
/* 0 */ 0, /* 0 */ 0,
/* 16 */ 16, 32, 48, 64, 80, 96, 112, 128, /* 16 */ 16, 32, 48, 64, 80, 96, 112, 128,
@ -13,12 +13,7 @@ static size_t size_classes[] = {
/* 256 */ 1280, 1536, 1792, 2048, /* 256 */ 1280, 1536, 1792, 2048,
/* 512 */ 2560, 3072, 3584, 4096, /* 512 */ 2560, 3072, 3584, 4096,
/* 1024 */ 5120, 6144, 7168, 8192, /* 1024 */ 5120, 6144, 7168, 8192,
/* 2048 */ 10240, 12288, 14336, 16384, /* 2048 */ 10240, 12288, 14336, 16384
#if CONFIG_EXTENDED_SIZE_CLASSES
/* 4096 */ 20480, 24576, 28672, 32768,
/* 8192 */ 40960, 49152, 57344, 65536,
/* 16384 */ 81920, 98304, 114688, 131072,
#endif
}; };
#define N_SIZE_CLASSES (sizeof(size_classes) / sizeof(size_classes[0])) #define N_SIZE_CLASSES (sizeof(size_classes) / sizeof(size_classes[0]))
@ -32,9 +27,9 @@ int main(void) {
void *p[N_SIZE_CLASSES]; void *p[N_SIZE_CLASSES];
for (unsigned i = 0; i < N_SIZE_CLASSES; i++) { for (unsigned i = 0; i < N_SIZE_CLASSES; i++) {
size_t size = size_classes[i]; unsigned size = size_classes[i];
p[i] = malloc(size); p[i] = malloc(size);
if (!p[i]) { if (!p) {
return 1; return 1;
} }
void *q = malloc(size); void *q = malloc(size);

View file

@ -1,15 +0,0 @@
#include <malloc.h>
#include <stdlib.h>
#include "test_util.h"
OPTNONE int main(void) {
char *p = malloc(256 * 1024);
if (!p) {
return 1;
}
size_t size = malloc_usable_size(p);
*(p + size) = 0;
free(p);
return 0;
}

View file

@ -1,15 +0,0 @@
#include <malloc.h>
#include <stdlib.h>
#include "test_util.h"
OPTNONE int main(void) {
char *p = malloc(256 * 1024);
if (!p) {
return 1;
}
size_t size = malloc_usable_size(p);
*(p + size + 7) = 0;
free(p);
return 0;
}

View file

@ -1,15 +0,0 @@
#include <malloc.h>
#include <stdlib.h>
#include "test_util.h"
OPTNONE int main(void) {
char *p = malloc(8);
if (!p) {
return 1;
}
size_t size = malloc_usable_size(p);
*(p + size) = 1;
free(p);
return 0;
}

View file

@ -1,16 +0,0 @@
#include <malloc.h>
#include <stdlib.h>
#include "test_util.h"
OPTNONE int main(void) {
char *p = malloc(8);
if (!p) {
return 1;
}
size_t size = malloc_usable_size(p);
// XOR is used to avoid the test having a 1/256 chance to fail
*(p + size + 7) ^= 1;
free(p);
return 0;
}

View file

@ -1,21 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "test_util.h"
OPTNONE int main(void) {
char *p = malloc(256 * 1024);
if (!p) {
return 1;
}
memset(p, 'a', 16);
free(p);
for (size_t i = 0; i < 256 * 1024; i++) {
printf("%x\n", p[i]);
if (p[i] != '\0') {
return 1;
}
}
return 0;
}

View file

@ -1,33 +0,0 @@
#include <pthread.h>
#include <stdlib.h>
static void *thread_func(void *arg) {
arg = realloc(arg, 1024);
if (!arg) {
exit(EXIT_FAILURE);
}
free(arg);
return NULL;
}
int main(void) {
void *mem = realloc(NULL, 12);
if (!mem) {
return EXIT_FAILURE;
}
pthread_t thread;
int r = pthread_create(&thread, NULL, thread_func, mem);
if (r != 0) {
return EXIT_FAILURE;
}
r = pthread_join(thread, NULL);
if (r != 0) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

View file

@ -0,0 +1,25 @@
delete_type_size_mismatch
double_free_large
double_free_large_delayed
double_free_small
double_free_small_delayed
eight_byte_overflow_large
eight_byte_overflow_small
invalid_free_protected
invalid_free_small_region
invalid_free_small_region_far
invalid_free_unprotected
read_after_free_large
read_after_free_small
read_zero_size
string_overflow
unaligned_free_large
unaligned_free_small
uninitialized_free
uninitialized_malloc_usable_size
uninitialized_realloc
write_after_free_large
write_after_free_large_reuse
write_after_free_small
write_after_free_small_reuse
write_zero_size

View file

@ -0,0 +1,31 @@
EXECUTABLES := \
double_free_large \
double_free_large_delayed \
double_free_small \
double_free_small_delayed \
unaligned_free_large \
unaligned_free_small \
read_after_free_large \
read_after_free_small \
write_after_free_large \
write_after_free_large_reuse \
write_after_free_small \
write_after_free_small_reuse \
read_zero_size \
write_zero_size \
invalid_free_protected \
invalid_free_unprotected \
invalid_free_small_region \
invalid_free_small_region_far \
uninitialized_free \
uninitialized_realloc \
uninitialized_malloc_usable_size \
eight_byte_overflow_small \
eight_byte_overflow_large \
string_overflow \
delete_type_size_mismatch
all: $(EXECUTABLES)
clean:
rm -f $(EXECUTABLES)

View file

@ -1,12 +1,11 @@
#include <stdint.h> #include <stdint.h>
#include "test_util.h"
struct foo { struct foo {
uint64_t a, b, c, d; uint64_t a, b, c, d;
}; };
OPTNONE int main(void) { __attribute__((optimize(0)))
int main(void) {
void *p = new char; void *p = new char;
struct foo *c = (struct foo *)p; struct foo *c = (struct foo *)p;
delete c; delete c;

View file

@ -1,9 +1,8 @@
#include <stdlib.h> #include <stdlib.h>
#include "test_util.h" __attribute__((optimize(0)))
int main(void) {
OPTNONE int main(void) { void *p = malloc(128 * 1024);
void *p = malloc(256 * 1024);
if (!p) { if (!p) {
return 1; return 1;
} }

View file

@ -1,13 +1,12 @@
#include <stdlib.h> #include <stdlib.h>
#include "test_util.h" __attribute__((optimize(0)))
int main(void) {
OPTNONE int main(void) { void *p = malloc(128 * 1024);
void *p = malloc(256 * 1024);
if (!p) { if (!p) {
return 1; return 1;
} }
void *q = malloc(256 * 1024); void *q = malloc(128 * 1024);
if (!q) { if (!q) {
return 1; return 1;
} }

View file

@ -1,8 +1,7 @@
#include <stdlib.h> #include <stdlib.h>
#include "test_util.h" __attribute__((optimize(0)))
int main(void) {
OPTNONE int main(void) {
void *p = malloc(16); void *p = malloc(16);
if (!p) { if (!p) {
return 1; return 1;

View file

@ -1,8 +1,7 @@
#include <stdlib.h> #include <stdlib.h>
#include "test_util.h" __attribute__((optimize(0)))
int main(void) {
OPTNONE int main(void) {
void *p = malloc(16); void *p = malloc(16);
if (!p) { if (!p) {
return 1; return 1;

View file

@ -0,0 +1,12 @@
#include <stdlib.h>
__attribute__((optimize(0)))
int main(void) {
char *p = malloc(128 * 1024);
if (!p) {
return 1;
}
*(p + 128 * 1024 + 7) = 0;
free(p);
return 0;
}

View file

@ -0,0 +1,12 @@
#include <stdlib.h>
__attribute__((optimize(0)))
int main(void) {
char *p = malloc(8);
if (!p) {
return 1;
}
*(p + 8 + 7) = 0;
free(p);
return 0;
}

View file

@ -2,9 +2,8 @@
#include <sys/mman.h> #include <sys/mman.h>
#include "test_util.h" __attribute__((optimize(0)))
int main(void) {
OPTNONE int main(void) {
free(malloc(16)); free(malloc(16));
char *p = mmap(NULL, 4096 * 16, PROT_NONE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); char *p = mmap(NULL, 4096 * 16, PROT_NONE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
if (p == MAP_FAILED) { if (p == MAP_FAILED) {

View file

@ -1,8 +1,7 @@
#include <stdlib.h> #include <stdlib.h>
#include "test_util.h" __attribute__((optimize(0)))
int main(void) {
OPTNONE int main(void) {
char *p = malloc(16); char *p = malloc(16);
if (!p) { if (!p) {
return 1; return 1;

View file

@ -1,8 +1,7 @@
#include <stdlib.h> #include <stdlib.h>
#include "test_util.h" __attribute__((optimize(0)))
int main(void) {
OPTNONE int main(void) {
char *p = malloc(16); char *p = malloc(16);
if (!p) { if (!p) {
return 1; return 1;

View file

@ -2,9 +2,8 @@
#include <sys/mman.h> #include <sys/mman.h>
#include "test_util.h" __attribute__((optimize(0)))
int main(void) {
OPTNONE int main(void) {
free(malloc(16)); free(malloc(16));
char *p = mmap(NULL, 4096 * 16, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); char *p = mmap(NULL, 4096 * 16, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
if (p == MAP_FAILED) { if (p == MAP_FAILED) {

View file

@ -0,0 +1,17 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
__attribute__((optimize(0)))
int main(void) {
char *p = malloc(128 * 1024);
if (!p) {
return 1;
}
memset(p, 'a', 16);
free(p);
for (size_t i = 0; i < 128 * 1024; i++) {
printf("%x\n", p[i]);
}
return 0;
}

View file

@ -2,9 +2,8 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "test_util.h" __attribute__((optimize(0)))
int main(void) {
OPTNONE int main(void) {
char *p = malloc(16); char *p = malloc(16);
if (!p) { if (!p) {
return 1; return 1;
@ -13,9 +12,6 @@ OPTNONE int main(void) {
free(p); free(p);
for (size_t i = 0; i < 16; i++) { for (size_t i = 0; i < 16; i++) {
printf("%x\n", p[i]); printf("%x\n", p[i]);
if (p[i] != '\0') {
return 1;
}
} }
return 0; return 0;
} }

View file

@ -1,9 +1,8 @@
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include "test_util.h" __attribute__((optimize(0)))
int main(void) {
OPTNONE int main(void) {
char *p = malloc(0); char *p = malloc(0);
if (!p) { if (!p) {
return 1; return 1;

View file

@ -4,9 +4,8 @@
#include <malloc.h> #include <malloc.h>
#include "test_util.h" __attribute__((optimize(0)))
int main(void) {
OPTNONE int main(void) {
char *p = malloc(16); char *p = malloc(16);
if (!p) { if (!p) {
return 1; return 1;

View file

@ -1,9 +1,8 @@
#include <stdlib.h> #include <stdlib.h>
#include "test_util.h" __attribute__((optimize(0)))
int main(void) {
OPTNONE int main(void) { char *p = malloc(128 * 1024);
char *p = malloc(256 * 1024);
if (!p) { if (!p) {
return 1; return 1;
} }

View file

@ -1,8 +1,7 @@
#include <stdlib.h> #include <stdlib.h>
#include "test_util.h" __attribute__((optimize(0)))
int main(void) {
OPTNONE int main(void) {
char *p = malloc(16); char *p = malloc(16);
if (!p) { if (!p) {
return 1; return 1;

View file

@ -1,8 +1,7 @@
#include <stdlib.h> #include <stdlib.h>
#include "test_util.h" __attribute__((optimize(0)))
int main(void) {
OPTNONE int main(void) {
free((void *)1); free((void *)1);
return 0; return 0;
} }

View file

@ -1,8 +1,7 @@
#include <malloc.h> #include <malloc.h>
#include "test_util.h" __attribute__((optimize(0)))
int main(void) {
OPTNONE int main(void) {
malloc_usable_size((void *)1); malloc_usable_size((void *)1);
return 0; return 0;
} }

View file

@ -1,8 +1,7 @@
#include <stdlib.h> #include <stdlib.h>
#include "test_util.h" __attribute__((optimize(0)))
int main(void) {
OPTNONE int main(void) {
void *p = realloc((void *)1, 16); void *p = realloc((void *)1, 16);
if (!p) { if (!p) {
return 1; return 1;

View file

@ -1,9 +1,9 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#include "test_util.h" __attribute__((optimize(0)))
int main(void) {
OPTNONE int main(void) { char *p = malloc(128 * 1024);
char *p = malloc(256 * 1024);
if (!p) { if (!p) {
return 1; return 1;
} }

View file

@ -0,0 +1,14 @@
#include <stdlib.h>
#include <string.h>
__attribute__((optimize(0)))
int main(void) {
char *p = malloc(128 * 1024);
if (!p) {
return 1;
}
free(p);
char *q = malloc(128 * 1024);
p[64 * 1024 + 1] = 'a';
return 0;
}

View file

@ -1,8 +1,8 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#include "test_util.h" __attribute__((optimize(0)))
int main(void) {
OPTNONE int main(void) {
char *p = malloc(128); char *p = malloc(128);
if (!p) { if (!p) {
return 1; return 1;

View file

@ -1,15 +1,14 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#include "test_util.h" __attribute__((optimize(0)))
#include "../util.h" int main(void) {
OPTNONE int main(void) {
char *p = malloc(128); char *p = malloc(128);
if (!p) { if (!p) {
return 1; return 1;
} }
free(p); free(p);
UNUSED char *q = malloc(128); char *q = malloc(128);
p[65] = 'a'; p[65] = 'a';

View file

@ -1,8 +1,8 @@
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h>
#include "test_util.h" __attribute__((optimize(0)))
int main(void) {
OPTNONE int main(void) {
char *p = malloc(0); char *p = malloc(0);
if (!p) { if (!p) {
return 1; return 1;

View file

@ -1,242 +0,0 @@
import os
import subprocess
import unittest
class TestSimpleMemoryCorruption(unittest.TestCase):
@classmethod
def setUpClass(self):
self.dir = os.path.dirname(os.path.realpath(__file__))
def run_test(self, test_name):
sub = subprocess.Popen(self.dir + "/" + test_name,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = sub.communicate()
return stdout, stderr, sub.returncode
def test_delete_type_size_mismatch(self):
_stdout, stderr, returncode = self.run_test(
"delete_type_size_mismatch")
self.assertEqual(returncode, -6)
self.assertEqual(stderr.decode(
"utf-8"), "fatal allocator error: sized deallocation mismatch (small)\n")
def test_double_free_large_delayed(self):
_stdout, stderr, returncode = self.run_test(
"double_free_large_delayed")
self.assertEqual(returncode, -6)
self.assertEqual(stderr.decode("utf-8"),
"fatal allocator error: invalid free\n")
def test_double_free_large(self):
_stdout, stderr, returncode = self.run_test("double_free_large")
self.assertEqual(returncode, -6)
self.assertEqual(stderr.decode("utf-8"),
"fatal allocator error: invalid free\n")
def test_double_free_small_delayed(self):
_stdout, stderr, returncode = self.run_test(
"double_free_small_delayed")
self.assertEqual(returncode, -6)
self.assertEqual(stderr.decode("utf-8"),
"fatal allocator error: double free (quarantine)\n")
def test_double_free_small(self):
_stdout, stderr, returncode = self.run_test("double_free_small")
self.assertEqual(returncode, -6)
self.assertEqual(stderr.decode("utf-8"),
"fatal allocator error: double free (quarantine)\n")
def test_overflow_large_1_byte(self):
_stdout, _stderr, returncode = self.run_test(
"overflow_large_1_byte")
self.assertEqual(returncode, -11)
def test_overflow_large_8_byte(self):
_stdout, _stderr, returncode = self.run_test(
"overflow_large_8_byte")
self.assertEqual(returncode, -11)
def test_overflow_small_1_byte(self):
_stdout, stderr, returncode = self.run_test(
"overflow_small_1_byte")
self.assertEqual(returncode, -6)
self.assertEqual(stderr.decode("utf-8"),
"fatal allocator error: canary corrupted\n")
def test_overflow_small_8_byte(self):
_stdout, stderr, returncode = self.run_test(
"overflow_small_8_byte")
self.assertEqual(returncode, -6)
self.assertEqual(stderr.decode("utf-8"),
"fatal allocator error: canary corrupted\n")
def test_invalid_free_protected(self):
_stdout, stderr, returncode = self.run_test("invalid_free_protected")
self.assertEqual(returncode, -6)
self.assertEqual(stderr.decode("utf-8"),
"fatal allocator error: invalid free\n")
def test_invalid_free_small_region_far(self):
_stdout, stderr, returncode = self.run_test(
"invalid_free_small_region_far")
self.assertEqual(returncode, -6)
self.assertEqual(stderr.decode(
"utf-8"), "fatal allocator error: invalid free within a slab yet to be used\n")
def test_invalid_free_small_region(self):
_stdout, stderr, returncode = self.run_test(
"invalid_free_small_region")
self.assertEqual(returncode, -6)
self.assertEqual(stderr.decode("utf-8"),
"fatal allocator error: double free\n")
def test_invalid_free_unprotected(self):
_stdout, stderr, returncode = self.run_test("invalid_free_unprotected")
self.assertEqual(returncode, -6)
self.assertEqual(stderr.decode("utf-8"),
"fatal allocator error: invalid free\n")
def test_invalid_malloc_usable_size_small_quarantene(self):
_stdout, stderr, returncode = self.run_test(
"invalid_malloc_usable_size_small_quarantine")
self.assertEqual(returncode, -6)
self.assertEqual(stderr.decode(
"utf-8"), "fatal allocator error: invalid malloc_usable_size (quarantine)\n")
def test_invalid_malloc_usable_size_small(self):
_stdout, stderr, returncode = self.run_test(
"invalid_malloc_usable_size_small")
self.assertEqual(returncode, -6)
self.assertEqual(stderr.decode(
"utf-8"), "fatal allocator error: invalid malloc_usable_size\n")
def test_read_after_free_large(self):
_stdout, _stderr, returncode = self.run_test("read_after_free_large")
self.assertEqual(returncode, -11)
def test_read_after_free_small(self):
stdout, _stderr, returncode = self.run_test("read_after_free_small")
self.assertEqual(returncode, 0)
self.assertEqual(stdout.decode("utf-8"),
"0\n0\n0\n0\n0\n0\n0\n0\n0\n0\n0\n0\n0\n0\n0\n0\n")
def test_read_zero_size(self):
_stdout, _stderr, returncode = self.run_test("read_zero_size")
self.assertEqual(returncode, -11)
def test_string_overflow(self):
stdout, _stderr, returncode = self.run_test("string_overflow")
self.assertEqual(returncode, 0)
self.assertEqual(stdout.decode("utf-8"), "overflow by 0 bytes\n")
def test_unaligned_free_large(self):
_stdout, stderr, returncode = self.run_test("unaligned_free_large")
self.assertEqual(returncode, -6)
self.assertEqual(stderr.decode("utf-8"),
"fatal allocator error: invalid free\n")
def test_unaligned_free_small(self):
_stdout, stderr, returncode = self.run_test("unaligned_free_small")
self.assertEqual(returncode, -6)
self.assertEqual(stderr.decode("utf-8"),
"fatal allocator error: invalid unaligned free\n")
def test_unaligned_malloc_usable_size_small(self):
_stdout, stderr, returncode = self.run_test(
"unaligned_malloc_usable_size_small")
self.assertEqual(returncode, -6)
self.assertEqual(stderr.decode("utf-8"),
"fatal allocator error: invalid unaligned malloc_usable_size\n")
def test_uninitialized_free(self):
_stdout, stderr, returncode = self.run_test("uninitialized_free")
self.assertEqual(returncode, -6)
self.assertEqual(stderr.decode("utf-8"),
"fatal allocator error: invalid free\n")
def test_uninitialized_malloc_usable_size(self):
_stdout, stderr, returncode = self.run_test(
"uninitialized_malloc_usable_size")
self.assertEqual(returncode, -6)
self.assertEqual(stderr.decode("utf-8"),
"fatal allocator error: invalid malloc_usable_size\n")
def test_uninitialized_realloc(self):
_stdout, stderr, returncode = self.run_test("uninitialized_realloc")
self.assertEqual(returncode, -6)
self.assertEqual(stderr.decode("utf-8"),
"fatal allocator error: invalid realloc\n")
def test_write_after_free_large_reuse(self):
_stdout, _stderr, returncode = self.run_test(
"write_after_free_large_reuse")
self.assertEqual(returncode, -11)
def test_write_after_free_large(self):
_stdout, _stderr, returncode = self.run_test("write_after_free_large")
self.assertEqual(returncode, -11)
def test_write_after_free_small_reuse(self):
_stdout, stderr, returncode = self.run_test(
"write_after_free_small_reuse")
self.assertEqual(returncode, -6)
self.assertEqual(stderr.decode("utf-8"),
"fatal allocator error: detected write after free\n")
def test_write_after_free_small(self):
_stdout, stderr, returncode = self.run_test("write_after_free_small")
self.assertEqual(returncode, -6)
self.assertEqual(stderr.decode("utf-8"),
"fatal allocator error: detected write after free\n")
def test_write_zero_size(self):
_stdout, _stderr, returncode = self.run_test("write_zero_size")
self.assertEqual(returncode, -11)
def test_malloc_object_size(self):
_stdout, _stderr, returncode = self.run_test("malloc_object_size")
self.assertEqual(returncode, 0)
def test_malloc_object_size_offset(self):
_stdout, _stderr, returncode = self.run_test(
"malloc_object_size_offset")
self.assertEqual(returncode, 0)
def test_invalid_malloc_object_size_small(self):
_stdout, stderr, returncode = self.run_test(
"invalid_malloc_object_size_small")
self.assertEqual(returncode, -6)
self.assertEqual(stderr.decode(
"utf-8"), "fatal allocator error: invalid malloc_object_size\n")
def test_invalid_malloc_object_size_small_quarantine(self):
_stdout, stderr, returncode = self.run_test(
"invalid_malloc_object_size_small_quarantine")
self.assertEqual(returncode, -6)
self.assertEqual(stderr.decode(
"utf-8"), "fatal allocator error: invalid malloc_object_size (quarantine)\n")
def test_impossibly_large_malloc(self):
_stdout, stderr, returncode = self.run_test(
"impossibly_large_malloc")
self.assertEqual(returncode, 0)
def test_uninitialized_read_small(self):
_stdout, stderr, returncode = self.run_test(
"uninitialized_read_small")
self.assertEqual(returncode, 0)
def test_uninitialized_read_large(self):
_stdout, stderr, returncode = self.run_test(
"uninitialized_read_large")
self.assertEqual(returncode, 0)
def test_realloc_init(self):
_stdout, _stderr, returncode = self.run_test(
"realloc_init")
self.assertEqual(returncode, 0)
if __name__ == '__main__':
unittest.main()

View file

@ -1,10 +0,0 @@
#ifndef TEST_UTIL_H
#define TEST_UTIL_H
#ifdef __clang__
#define OPTNONE __attribute__((optnone))
#else
#define OPTNONE __attribute__((optimize(0)))
#endif
#endif

View file

@ -1,12 +0,0 @@
#include <malloc.h>
#include "test_util.h"
OPTNONE int main(void) {
char *p = malloc(16);
if (!p) {
return 1;
}
malloc_usable_size(p + 1);
return 0;
}

View file

@ -1,14 +0,0 @@
#include <stdlib.h>
#include "test_util.h"
OPTNONE int main(void) {
char *p = malloc(256 * 1024);
for (unsigned i = 0; i < 256 * 1024; i++) {
if (p[i] != 0) {
return 1;
}
}
free(p);
return 0;
}

View file

@ -1,14 +0,0 @@
#include <stdlib.h>
#include "test_util.h"
OPTNONE int main(void) {
char *p = malloc(8);
for (unsigned i = 0; i < 8; i++) {
if (p[i] != 0) {
return 1;
}
}
free(p);
return 0;
}

View file

@ -1,16 +0,0 @@
#include <stdlib.h>
#include <string.h>
#include "test_util.h"
#include "../util.h"
OPTNONE int main(void) {
char *p = malloc(256 * 1024);
if (!p) {
return 1;
}
free(p);
UNUSED char *q = malloc(256 * 1024);
p[64 * 1024 + 1] = 'a';
return 0;
}

3661
third_party/libdivide.h vendored

File diff suppressed because it is too large Load diff

13
util.c
View file

@ -4,15 +4,8 @@
#include <unistd.h> #include <unistd.h>
#ifdef __ANDROID__
#include <async_safe/log.h>
int mallopt(int param, int value);
#define M_BIONIC_RESTORE_DEFAULT_SIGABRT_HANDLER (-1003)
#endif
#include "util.h" #include "util.h"
#ifndef __ANDROID__
static int write_full(int fd, const char *buf, size_t length) { static int write_full(int fd, const char *buf, size_t length) {
do { do {
ssize_t bytes_written = write(fd, buf, length); ssize_t bytes_written = write(fd, buf, length);
@ -28,17 +21,11 @@ static int write_full(int fd, const char *buf, size_t length) {
return 0; return 0;
} }
#endif
COLD noreturn void fatal_error(const char *s) { COLD noreturn void fatal_error(const char *s) {
#ifdef __ANDROID__
mallopt(M_BIONIC_RESTORE_DEFAULT_SIGABRT_HANDLER, 0);
async_safe_fatal("hardened_malloc: fatal allocator error: %s", s);
#else
const char *prefix = "fatal allocator error: "; const char *prefix = "fatal allocator error: ";
(void)(write_full(STDERR_FILENO, prefix, strlen(prefix)) != -1 && (void)(write_full(STDERR_FILENO, prefix, strlen(prefix)) != -1 &&
write_full(STDERR_FILENO, s, strlen(s)) != -1 && write_full(STDERR_FILENO, s, strlen(s)) != -1 &&
write_full(STDERR_FILENO, "\n", 1)); write_full(STDERR_FILENO, "\n", 1));
abort(); abort();
#endif
} }

75
util.h
View file

@ -1,17 +1,11 @@
#ifndef UTIL_H #ifndef UTIL_H
#define UTIL_H #define UTIL_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <stdnoreturn.h>
// C11 noreturn doesn't work in C++
#define noreturn __attribute__((noreturn))
#define likely(x) __builtin_expect(!!(x), 1) #define likely(x) __builtin_expect(!!(x), 1)
#define likely51(x) __builtin_expect_with_probability(!!(x), 1, 0.51)
#define unlikely(x) __builtin_expect(!!(x), 0) #define unlikely(x) __builtin_expect(!!(x), 0)
#define unlikely51(x) __builtin_expect_with_probability(!!(x), 0, 0.51)
#define min(x, y) ({ \ #define min(x, y) ({ \
__typeof__(x) _x = (x); \ __typeof__(x) _x = (x); \
@ -32,12 +26,11 @@
#define STRINGIFY(s) #s #define STRINGIFY(s) #s
#define ALIAS(f) __attribute__((alias(STRINGIFY(f)))) #define ALIAS(f) __attribute__((alias(STRINGIFY(f))))
// supported since GCC 15 static inline int ffzl(long x) {
#if __has_attribute (nonstring) return __builtin_ffsl(~x);
# define NONSTRING __attribute__ ((nonstring)) }
#else
# define NONSTRING COLD noreturn void fatal_error(const char *s);
#endif
typedef uint8_t u8; typedef uint8_t u8;
typedef uint16_t u16; typedef uint16_t u16;
@ -45,50 +38,28 @@ typedef uint32_t u32;
typedef uint64_t u64; typedef uint64_t u64;
typedef unsigned __int128 u128; typedef unsigned __int128 u128;
#define U64_WIDTH 64 // use __register_atfork directly to avoid linking with libpthread for glibc < 2.28
#ifdef __GLIBC__
#if !__GLIBC_PREREQ(2, 28)
extern void *__dso_handle;
extern int __register_atfork(void (*)(void), void (*)(void), void (*)(void), void *);
#define atfork(prepare, parent, child) __register_atfork(prepare, parent, child, __dso_handle)
#endif
#endif
static inline int ffz64(u64 x) { #ifndef atfork
return __builtin_ffsll(~x); #define atfork pthread_atfork
} #endif
// parameter must not be 0 #ifdef CONFIG_SEAL_METADATA
static inline int clz64(u64 x) {
return __builtin_clzll(x);
}
// parameter must not be 0
static inline u64 log2u64(u64 x) {
return U64_WIDTH - clz64(x) - 1;
}
static inline size_t align(size_t size, size_t align) {
size_t mask = align - 1;
return (size + mask) & ~mask;
}
// u4_arr_{set,get} are helper functions for using u8 array as an array of unsigned 4-bit values.
// val is treated as a 4-bit value
static inline void u4_arr_set(u8 *arr, size_t idx, u8 val) {
size_t off = idx >> 1;
size_t shift = (idx & 1) << 2;
u8 mask = (u8) (0xf0 >> shift);
arr[off] = (arr[off] & mask) | (val << shift);
}
static inline u8 u4_arr_get(const u8 *arr, size_t idx) {
size_t off = idx >> 1;
size_t shift = (idx & 1) << 2;
return (u8) ((arr[off] >> shift) & 0xf);
}
COLD noreturn void fatal_error(const char *s);
#if CONFIG_SEAL_METADATA
#ifdef __GLIBC__ #ifdef __GLIBC__
#if __GLIBC_PREREQ(2, 27)
#define USE_PKEY #define USE_PKEY
#else #endif
#endif
#ifndef USE_PKEY
#error "CONFIG_SEAL_METADATA requires Memory Protection Key support" #error "CONFIG_SEAL_METADATA requires Memory Protection Key support"
#endif #endif