Date: Thu, 05 Jun 2025 20:41:29 -0700
> On Jun 5, 2025, at 5:49 PM, Frederick Virchanza Gotham via Std-Proposals <std-proposals_at_[hidden]> wrote:
>
> On Tue, Jun 3, 2025 at 12:29 PM Oliver Hunt wrote:
>>>
>>>
>>> (1) Get the vtable pointer from the object, conveniently always located at [base + 0x00]
>>> (2) Dereference the vtable pointer and substract 16 to yield the address of the "distance
>>> to most derived" offset.
>>
>> And this faults immediately because you failed to authenticate the load, and so the load is invalid.
>>
>>> (3) Add the offset to the address of the sub-object.
>>
>> You don’t get this far.
>
>
> I gave up trying to run Qemu in user mode on an x86_64 macOS computer
> to run an arm64 macOS executable.
>
> So I'm using the Github Actions runner, 'macos-15', to build and run
> my test program.
>
> Here's the code I've got up on GitHub:
>
> https://github.com/healytpk/polyhandle/blob/main/research_machine_code.cpp
>
> It fails to link. I have identified the compiler option that's causing
> the problem, it's "-fptrauth-calls". If I use this compiler option
> then I get linker relocation errors about 'std::type_info' as follows:
>
> ld: relocation at '__ZTV4Frog'+0x0018 is not supported:
> r_address=0x48, r_type=11, r_extern=1, r_pcrel=0, r_length=3 in
> research_machine_code.o'
> clang++: error: linker command failed with exit code 1 (use -v to
> see invocation)
>
> Note that '__ZTV4Frog' is the mangled name for the 'std::type_info'
> for the 'Frog' class.
>
> Note that I still get these errors even if I use "-fno-rtti". I've
> been searching the web and asking AI but I can't get this code to
> compile.
>
> I'm confident that I can figure out getting 'polyhandle' to work on
> new Apple computers with pointer authentication, but first I need to
> figure out why "-fptrauth-calls" is giving linker errors.
Ignoring any of the technical details of how ptrauth works that’s weird: are you
getting this from clang?
What is your test case and build args? If clang can you also provide the output
from your command line with the addition -### argument?
I’m asking because there’s no reason any ptrauth flag should impact the symbols
emitted or referenced so it’s super weird and might imply a bug in some configurations.
>
> Here's my end game which I think might work:
>
> A pointer on arm64 is 64-Bit, however on arm64 you can only
> address 256 terrabytes of memory. So only 49 of those bits are needed
> for a memory address. That leaves 15 bits to play around with. Plus,
> since we know that any polymorphic object begins with a vtable
> pointer, this means that alignof(any polymorphic class) >= 8. This
> means that we can play around also with the lowest 3 bits because
> they'll always be zero. So in total we have 18 bits we can manipulate
> for our own purposes. The secret number needed to decode the vtable is
> only 16 bits. So I'll put that 16-bit secret number + 46 bits memory
> address into a " void * ". And I'll still have two bits left over to
> play around with.
Those bits are definitely not available for use under pointer authentication. I don’t
think they’re available with CHERI, and I don’t think you’re guaranteed access on
systems with MTE (but the semantics of MTE are very different from CHERI and
pointer auth - they relate the pointer to an address with the memory at that address,
and don’t impact codegen).
Also I don’t know what other platforms do, but in darwin/xnu the highboys are
all 1s in kernel space and all zeroes in userspace, so even if these bits were
available you’d need to know what ring your code was running in.
What you are trying to do is not something that is possible under the standard,
and the reason the standard doesn’t make it possible is precisely to permit these
different implementations and ABIs. It is not a spec bug, but a feature.
Ignoring the problems of ABI differences, it is also not something that makes
sense from a type theoretic point of view - C++ does not have a polymorphic
bottom type like Java/.NET's Object, NSCell, etc, trying to treat void* as if it
was is not consistent at a theoretical level.
If you want to be able to interact with a universal tree of polymorphic types, create
a common polymorphic base class, or use a wrapper like this silly thing I just wrote:
https://godbolt.org/z/339KTnWT9
(I really did just write this, do not use it in production it provides no lifetime safety guarantees[1])
Also because I finally worked out how to do diffs, I made this: https://godbolt.org/z/PqoenEK5x
The only difference is the name of the class, and you can see the authentication schema for
both the load of the vtable pointer and the load of the vtable slot have different schemas.
To call a virtual function you need to know the type of the target, and the type and name of the
target function. Getting either wrong will cause your process to be terminated.
> I'm not absolutely certain that the above paragraph will work -- but
> even if it doesn't, I've got two other strategies in mind. I _will_
> get 'polyhandle' working on pointer authenticated systems, one way or
> another. The above paragraph would be ideal though.
What is your goal? We’re not looking for “I want to be able to get the typeid of an
object with an unknown type”, we’re asking for what the actual use case you have is,
because it’s possible that it is a sensible use case the language should address, or
even one that is already addressed. We just don’t know, because you keep not
answering this question.
Answering this question is really important: numerous people have tried to explain to
you why it is not sound to do what you’re trying to do within the C++ type system, and
multiple different platforms have ABIs that make it impossible to do what you want.
Again, that is why the C++ standard does not say anything about how an implementation
should make polymorphism work.
As a side note I have finally upstreamed __builtin_get_vtable_pointer - this is a thing
that is needed for terrible security/safety checks in some projects, and loading a vtable
pointer manually is hard when there’s pointer auth: https://godbolt.org/z/9rPn8d6TE
But even this - a compiler builtin that is just loading the vtable pointer - still requires
knowledge of the static type of the object pointer, because without that knowledge it
cannot authenticate the pointer.
—Oliver
[1] The lifetime guarantees (or rather the absolute lack of them :D) here are actually
interesting as an explanatory tool, as I realized that I can make an artificiall use after
free error to demonstrate why I designed the darwin pointer auth abi in this way:
https://godbolt.org/z/c7n5W7xTx
Normally it would not be quite as overtly obvious, but semantically it’s the same. Compiling
and running locally, targeting arm64 (which does not have the pointer auth extensions):
~ clang++ -arch arm64 -std=c++26 uaf.cpp && ./a.out
Type name: 3Bar
Type name: 3Foo
Compiling and running targeting arm64e (this is name apple uses for the ARMv8.3 + pointer
auth slice - which is used for all OS libraries and applications):
~ clang++ -arch arm64e -std=c++26 uaf.cpp && ./a.out
Type name: 3Bar
[1] 54323 bus error ./a.out
And finally, the reason that we incorporate the type into the authentication schema, we’ll explicitly
disable it - which only works here because we’re not vending this class outside of our own code -
in principle we can’t use any polymorphic objects from the os or standard library here [2]:
~ clang++ -arch arm64e -fno-ptrauth-vtable-pointer-type-discrimination -std=c++26 uaf.cpp && ./a.out
Type name: 3Bar
Type name: 3Foo
Very simply, a use after free based attack works by getting a program to use a pointer to an
object of one type after the object has been freed, and a new object of a different type has
been allocated in the same place in memory. If the pointer authentication schema does not
incorporate the type of the object, then it does not prevent the attack from succeeding.
Similarly the address of the vtable pointer (not the address of the vtable itself) is also incorporated,
as failing to include that would allow an attacker with arbitrary read or write primitives to copy
validly signed vtable pointer one object and place it on another.
[2] There’s some special/annoying behavior with type_info in darwin userspace due to historical
ABI issues, but that’s only darwin userspace - the caveats don’t apply in kernel space, any
restricted environments, or the linux pointer auth ABI.
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
> On Tue, Jun 3, 2025 at 12:29 PM Oliver Hunt wrote:
>>>
>>>
>>> (1) Get the vtable pointer from the object, conveniently always located at [base + 0x00]
>>> (2) Dereference the vtable pointer and substract 16 to yield the address of the "distance
>>> to most derived" offset.
>>
>> And this faults immediately because you failed to authenticate the load, and so the load is invalid.
>>
>>> (3) Add the offset to the address of the sub-object.
>>
>> You don’t get this far.
>
>
> I gave up trying to run Qemu in user mode on an x86_64 macOS computer
> to run an arm64 macOS executable.
>
> So I'm using the Github Actions runner, 'macos-15', to build and run
> my test program.
>
> Here's the code I've got up on GitHub:
>
> https://github.com/healytpk/polyhandle/blob/main/research_machine_code.cpp
>
> It fails to link. I have identified the compiler option that's causing
> the problem, it's "-fptrauth-calls". If I use this compiler option
> then I get linker relocation errors about 'std::type_info' as follows:
>
> ld: relocation at '__ZTV4Frog'+0x0018 is not supported:
> r_address=0x48, r_type=11, r_extern=1, r_pcrel=0, r_length=3 in
> research_machine_code.o'
> clang++: error: linker command failed with exit code 1 (use -v to
> see invocation)
>
> Note that '__ZTV4Frog' is the mangled name for the 'std::type_info'
> for the 'Frog' class.
>
> Note that I still get these errors even if I use "-fno-rtti". I've
> been searching the web and asking AI but I can't get this code to
> compile.
>
> I'm confident that I can figure out getting 'polyhandle' to work on
> new Apple computers with pointer authentication, but first I need to
> figure out why "-fptrauth-calls" is giving linker errors.
Ignoring any of the technical details of how ptrauth works that’s weird: are you
getting this from clang?
What is your test case and build args? If clang can you also provide the output
from your command line with the addition -### argument?
I’m asking because there’s no reason any ptrauth flag should impact the symbols
emitted or referenced so it’s super weird and might imply a bug in some configurations.
>
> Here's my end game which I think might work:
>
> A pointer on arm64 is 64-Bit, however on arm64 you can only
> address 256 terrabytes of memory. So only 49 of those bits are needed
> for a memory address. That leaves 15 bits to play around with. Plus,
> since we know that any polymorphic object begins with a vtable
> pointer, this means that alignof(any polymorphic class) >= 8. This
> means that we can play around also with the lowest 3 bits because
> they'll always be zero. So in total we have 18 bits we can manipulate
> for our own purposes. The secret number needed to decode the vtable is
> only 16 bits. So I'll put that 16-bit secret number + 46 bits memory
> address into a " void * ". And I'll still have two bits left over to
> play around with.
Those bits are definitely not available for use under pointer authentication. I don’t
think they’re available with CHERI, and I don’t think you’re guaranteed access on
systems with MTE (but the semantics of MTE are very different from CHERI and
pointer auth - they relate the pointer to an address with the memory at that address,
and don’t impact codegen).
Also I don’t know what other platforms do, but in darwin/xnu the highboys are
all 1s in kernel space and all zeroes in userspace, so even if these bits were
available you’d need to know what ring your code was running in.
What you are trying to do is not something that is possible under the standard,
and the reason the standard doesn’t make it possible is precisely to permit these
different implementations and ABIs. It is not a spec bug, but a feature.
Ignoring the problems of ABI differences, it is also not something that makes
sense from a type theoretic point of view - C++ does not have a polymorphic
bottom type like Java/.NET's Object, NSCell, etc, trying to treat void* as if it
was is not consistent at a theoretical level.
If you want to be able to interact with a universal tree of polymorphic types, create
a common polymorphic base class, or use a wrapper like this silly thing I just wrote:
https://godbolt.org/z/339KTnWT9
(I really did just write this, do not use it in production it provides no lifetime safety guarantees[1])
Also because I finally worked out how to do diffs, I made this: https://godbolt.org/z/PqoenEK5x
The only difference is the name of the class, and you can see the authentication schema for
both the load of the vtable pointer and the load of the vtable slot have different schemas.
To call a virtual function you need to know the type of the target, and the type and name of the
target function. Getting either wrong will cause your process to be terminated.
> I'm not absolutely certain that the above paragraph will work -- but
> even if it doesn't, I've got two other strategies in mind. I _will_
> get 'polyhandle' working on pointer authenticated systems, one way or
> another. The above paragraph would be ideal though.
What is your goal? We’re not looking for “I want to be able to get the typeid of an
object with an unknown type”, we’re asking for what the actual use case you have is,
because it’s possible that it is a sensible use case the language should address, or
even one that is already addressed. We just don’t know, because you keep not
answering this question.
Answering this question is really important: numerous people have tried to explain to
you why it is not sound to do what you’re trying to do within the C++ type system, and
multiple different platforms have ABIs that make it impossible to do what you want.
Again, that is why the C++ standard does not say anything about how an implementation
should make polymorphism work.
As a side note I have finally upstreamed __builtin_get_vtable_pointer - this is a thing
that is needed for terrible security/safety checks in some projects, and loading a vtable
pointer manually is hard when there’s pointer auth: https://godbolt.org/z/9rPn8d6TE
But even this - a compiler builtin that is just loading the vtable pointer - still requires
knowledge of the static type of the object pointer, because without that knowledge it
cannot authenticate the pointer.
—Oliver
[1] The lifetime guarantees (or rather the absolute lack of them :D) here are actually
interesting as an explanatory tool, as I realized that I can make an artificiall use after
free error to demonstrate why I designed the darwin pointer auth abi in this way:
https://godbolt.org/z/c7n5W7xTx
Normally it would not be quite as overtly obvious, but semantically it’s the same. Compiling
and running locally, targeting arm64 (which does not have the pointer auth extensions):
~ clang++ -arch arm64 -std=c++26 uaf.cpp && ./a.out
Type name: 3Bar
Type name: 3Foo
Compiling and running targeting arm64e (this is name apple uses for the ARMv8.3 + pointer
auth slice - which is used for all OS libraries and applications):
~ clang++ -arch arm64e -std=c++26 uaf.cpp && ./a.out
Type name: 3Bar
[1] 54323 bus error ./a.out
And finally, the reason that we incorporate the type into the authentication schema, we’ll explicitly
disable it - which only works here because we’re not vending this class outside of our own code -
in principle we can’t use any polymorphic objects from the os or standard library here [2]:
~ clang++ -arch arm64e -fno-ptrauth-vtable-pointer-type-discrimination -std=c++26 uaf.cpp && ./a.out
Type name: 3Bar
Type name: 3Foo
Very simply, a use after free based attack works by getting a program to use a pointer to an
object of one type after the object has been freed, and a new object of a different type has
been allocated in the same place in memory. If the pointer authentication schema does not
incorporate the type of the object, then it does not prevent the attack from succeeding.
Similarly the address of the vtable pointer (not the address of the vtable itself) is also incorporated,
as failing to include that would allow an attacker with arbitrary read or write primitives to copy
validly signed vtable pointer one object and place it on another.
[2] There’s some special/annoying behavior with type_info in darwin userspace due to historical
ABI issues, but that’s only darwin userspace - the caveats don’t apply in kernel space, any
restricted environments, or the linux pointer auth ABI.
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
Received on 2025-06-06 03:41:46