New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement versioning system for DLLs #3239
base: master
Are you sure you want to change the base?
Conversation
Now spec2def uses the version annotations in spec files to generate a table, which will be used to dynamically patch the export table based on the current process' appcompat settings, when a dll is initialized, allowing to export the appropriate functions for each version. This feature is still disabled in config.cmake with the variable DISABLE_EXPORT_VERSIONING.
The loader checks for a ros-compat-descriptor, and if one is found, parses it's entries. Each entry corresponds to an export and provides a bitmask that specifies on what Windows version the export exists. If the bit for the process' appcompat version (default is still Windows 2003) is set, the export will be kept, otherwise the export will be moved into a second (private) export table. If a module has a ros-compat-descritor, it is still allowed to import these hidden exports.
so that winhttp can use nt6+ apis
This is required, because with dynamically patched export tables we will export all vista functions from the main dll, and you can link to them at compile time, but they will not be resolved at runtime, unless the importer has a roscompat-descriptor. Linking the vista version first will resolve the imports from the vista dll.
Create kernel32_vista_static library and link both kernel32_vista and kernel32 to it. Export some vista functions from kernel32.
@ stub __intrinsic_setjmp | ||
@ stub __intrinsic_setjmpex | ||
@ stub __processing_throw | ||
@ stub __report_gsfailure | ||
@ stub __std_exception_copy | ||
@ stub __std_exception_destroy | ||
@ stub -arch=x86_65 __std_terminate |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ stub -arch=x86_65 __std_terminate | |
@ stub -arch=x86_64 __std_terminate |
The test is mapping ntdll using MapViewOfFile, bypassing the loader, then enumerates it's exports, and then passes the names to GetProcAddress, which is hiding some of the original exports. At this point we do not support this scenario.
955bc17
to
42ffa67
Compare
My 2¢ on the problem listed in the OP: How often do applications legitimately (i.e., not testing against or depending on undocumented or semi-documented behavior) load a DLL using Also, what do you mean by “if an app looks up the entry manually”? If that means parsing the PE file format by hand in-memory to find, relocate, and call export addresses, this looks to me like an even rarer use-case (except maybe for heavily obfuscated software e.g. malware). The feasibility of this approach depends on how likely it is that normal, correctly written applications engage in these two behaviors, and you would have more knowledge of this than I. Hope this helps! |
I propose taking the current approach for now (neither of the 1-3), dropping the hack that makes the loader tests pass ok, open an issue to implement kernel-mode FS redirection approach (4) instead, add the issue number to the loader test to be printed before marking test as failed. Make it an explicitly acknowledged regression so that there will be an incentive to fix it. |
is this good to go ? (except for the x86_65 typo :) |
} | ||
|
||
void | ||
Output_RosCompatDescriptor(FILE *file, EXPORT *pexports, unsigned int cExports) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd suggest generating the regular export directory, not use the .def file and call the C file <module>_exports.c"
This way, everyone gets a generated C file and we are not bitten when we try to change this and that, because some modules have a stubs file and some don't
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why don't you simply merge xxx and xxx_vista modules ?
/// HACK: should make a copy | ||
qsort(pexports, cExports, sizeof(EXPORT), CompareExports); | ||
|
||
fprintf(file, "ULONG __roscompat_export_masks__[] =\n{\n"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this belong to the ".expvers" section ?
Peb->OSMinorVersion = AppCompatVersion & 0xFF; | ||
} | ||
|
||
DPRINT("roscompat: Patching eat of %wZ for 0x%x\n", &LdrEntry->BaseDllName, AppCompatVersion); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
eat ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
EAT (Export Address Table)
NTAPI | ||
LdrpApplyRosCompatMagic(PLDR_DATA_TABLE_ENTRY LdrEntry); | ||
|
||
extern char __ImageBase; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this used for ?
#if 0 | ||
static | ||
NTSTATUS | ||
GetExportNameVersionTable( | ||
_In_ PVOID ImageBase, | ||
_Out_ PULONG *Table, | ||
_Out_ PULONG NumberOfEntries) | ||
{ | ||
PROSCOMPAT_DESCRIPTOR RosCompatDescriptor; | ||
|
||
RosCompatDescriptor = FindRosCompatDescriptor(ImageBase); | ||
if (RosCompatDescriptor == NULL) | ||
{ | ||
return STATUS_NOT_FOUND; | ||
} | ||
|
||
*NumberOfEntries = RosCompatDescriptor->NumberOfExportNames; | ||
*Table = RosCompatDescriptor->ExportNameMasks; | ||
|
||
return STATUS_SUCCESS; | ||
} | ||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you don't use it, can you simply remove it ?
for (i = 0; j < RosCompatDescriptor->NumberOfExportNames; i++, j++) | ||
{ | ||
NameTable[j] = OrgNameTable[i]; | ||
OrdinalTable[j] = OrgOrdinalTable[i]; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for (i = 0; j < RosCompatDescriptor->NumberOfExportNames; i++, j++) | |
{ | |
NameTable[j] = OrgNameTable[i]; | |
OrdinalTable[j] = OrgOrdinalTable[i]; | |
} | |
RtlCopyMemory(&NameTable[j], OrgNameTable, (RosCompatDescriptor->NumberOfExportNames - j) * sizeof(OrgNameTable[0])) | |
RtlCopyMemory(&OrdinalTable[j], OrgOrdinalTable, (RosCompatDescriptor->NumberOfExportNames - j) * sizeof(OrgOrdinalTable[0])) |
for (i = 0, j = 0, k = 0; i < ExportDirectory->NumberOfNames; i++) | ||
{ | ||
if (RosCompatDescriptor->ExportNameMasks[i] & VersionMask) | ||
{ | ||
/* Put public functions into the export table */ | ||
NameTable[j] = OrgNameTable[i]; | ||
OrdinalTable[j] = OrgOrdinalTable[i]; | ||
j++; | ||
} | ||
else | ||
{ | ||
/* Move private functions to the start of the temp buffer */ | ||
OrgNameTable[k] = OrgNameTable[i]; | ||
OrgOrdinalTable[k] = OrgOrdinalTable[i]; | ||
k++; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How are we sure that we don't overflow beyond the name table ?
return Status; | ||
} | ||
|
||
/* Unprotect the export directory */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Protect again
if (ImportLdrEntry->PatchInformation != NULL) | ||
{ | ||
SnapFlags |= SNAP_PRIVATE; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you need this ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it make sense to have spec2def export every function, regardless of the target version ?
@@ -368,6 +368,7 @@ if(ARCH STREQUAL "i386") | |||
list(APPEND CRT_ASM_SOURCE | |||
except/i386/chkesp.s | |||
except/i386/prolog.s | |||
except/i386/ehandler-asm.s |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
amd64 ehandler-asm.s missing
@@ -67,7 +69,10 @@ if(ARCH STREQUAL "i386") | |||
math/i386/cisqrt.c) | |||
elseif(ARCH STREQUAL "amd64") | |||
list(APPEND MSVCRTEX_ASM_SOURCE | |||
except/amd64/chkstk_ms.s) | |||
except/amd64/chkstk_ms.s | |||
except/amd64/ehandler-asm.s) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
msvcrtex is not linked into msvcrt.dll
hello, any news ? |
any news ? |
Purpose
This PR implement a system that allows to make DLL exports version specific at runtime, based on the applications compatibility setting. It still allows ReactOS DLLs to statically import functions from higher versions, even if they are hidden to the application.
JIRA issue: CORE-11288
Proposed changes
Suggested to review by commit.
Problem
Currently there is the following problem: When an app maps a dll using MapViewOfFile it will see all exports unpatched, if it uses GetModuleHandle() it will get the patched version, so there will be inconsistencies between the exports. This was highlited by kernel32_winetest:loader, which is currently hacked to not crash on that situation. This should obviously be fixed.
Possible solutions:
Feedback/opinions welcome.
TODO