Skip to content
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

Open
wants to merge 15 commits into
base: master
Choose a base branch
from

Conversation

tkreuzer
Copy link
Contributor

@tkreuzer tkreuzer commented Sep 26, 2020

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

  • Add a feature to spec2def to generate a RosCompat descriptor (contained in a special section) pointing to a mask-array, that specifies for each named export on which Windows versions it is present.
  • Add a feature to the loader in NTDLL that checks for a RosCompat descriptor, and if present, uses it to patch the export table, splitting it into a public and private part.
  • Modify the loader to allow ReactOS DLLs (those that contain a RosCompat descriptor, even if it is empty) to import from private/hidden exports.

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:

  1. Patch RtlImageDirectoryEntryToData to detect an unpatched dll and apply the patching there, too. This would solve this scenario, but not the case, where an app looks up the entry manually.
  2. Patch NtMapViewOfSection in ntdll to apply the patching. This would solve all scenarios, except loading the file with raw file access. But it would also create an Nt* function in ntdll, which doesn't match the expected asm stub, breaking applications that enumerate system calls.
  3. Move the patching to kernel mode. This would solve the problem with the systemcall stub, but not the problem with direct file reading.
  4. Abandon the idea of patching the exports and instead develop a completely new system based on kernel mode file system redirection. That would theoretically solve all the issues, but is clearly somewhat tricky and would take a while to develop.
  5. Temporarily use one of the first 3 options, until option 4 is ready.
    Feedback/opinions welcome.

TODO

  • Run on testbot
  • Fix inconsistency problem

tkreuzer and others added 10 commits September 26, 2020 18:31
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.
@tkreuzer tkreuzer added the NT6+ For PRs that aim at implementing NT6+ functionality. label Sep 26, 2020
@tkreuzer tkreuzer requested a review from a team September 26, 2020 16:46
@tkreuzer tkreuzer self-assigned this Sep 26, 2020
@tkreuzer tkreuzer added this to New PRs in ReactOS PRs via automation Sep 26, 2020
@tkreuzer tkreuzer changed the title Appcompat dll export magic Implement versioning system for DLLs Sep 26, 2020
@ 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@ 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.
@wjk
Copy link
Contributor

wjk commented Sep 27, 2020

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 MapViewOfFile()? If it isn't that common a use-case, I would suggest option 1 until option 4 can be implemented (possibly on top of the SxS redirection system).

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!

@andrew-boyarshin
Copy link
Contributor

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.

@jschueller
Copy link
Contributor

is this good to go ? (except for the x86_65 typo :)

}

void
Output_RosCompatDescriptor(FILE *file, EXPORT *pexports, unsigned int cExports)
Copy link
Contributor

@zefklop zefklop Oct 16, 2020

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

Copy link
Contributor

@zefklop zefklop left a 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");
Copy link
Contributor

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eat ?

Copy link
Member

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;
Copy link
Contributor

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 ?

Comment on lines +143 to +164
#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
Copy link
Contributor

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 ?

Comment on lines +235 to +239
for (i = 0; j < RosCompatDescriptor->NumberOfExportNames; i++, j++)
{
NameTable[j] = OrgNameTable[i];
OrdinalTable[j] = OrgOrdinalTable[i];
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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]))

Comment on lines +212 to +228
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++;
}
}
Copy link
Contributor

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 */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Protect again

Comment on lines +137 to +140
if (ImportLdrEntry->PatchInformation != NULL)
{
SnapFlags |= SNAP_PRIVATE;
}
Copy link
Contributor

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 ?

Copy link
Contributor

@zefklop zefklop left a 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
Copy link
Contributor

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)
Copy link
Contributor

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

@jschueller
Copy link
Contributor

hello, any news ?

@IniKiwi
Copy link

IniKiwi commented Jan 18, 2023

any news ?

@binarymaster binarymaster removed this from New PRs in ReactOS PRs Feb 29, 2024
@binarymaster binarymaster added the enhancement For PRs with an enhancement/new feature. label Feb 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement For PRs with an enhancement/new feature. NT6+ For PRs that aim at implementing NT6+ functionality.
Projects
None yet
9 participants