mirror of
https://github.com/reactos/reactos.git
synced 2025-06-16 17:49:27 +00:00
[KERNEL32][RTL] Diverse fixes/improvements for thread stack creation code. (#802)
- kernel32!BaseCreateStack() is compatible with ntdll!RtlpCreateUserStack().
- When checking whether a stack guard page can be added, its size has to
be accounted for in the checking logic.
- We have to satisfy the PEB::MinimumStackCommit constraint.
- We cannot use PEB::GuaranteedStackBytes in BaseCreateStack() since it is
nowhere initialized (default is 0). It gets initialized to a non-zero
value when the user manually calls SetThreadStackGuarantee().
https://www.installsetupconfig.com/win32programming/windowsthreadsprocessapis7_6.html
- RtlpCreateUserStack(): Fix memory leak in failure case.
- RtlpFreeUserStack() doesn't need to return anything.
See also commit 1bc59379
(r59868).
CORE-11319
This commit is contained in:
parent
4895f21efa
commit
effdb6f232
4 changed files with 102 additions and 75 deletions
|
@ -165,9 +165,10 @@ CreateRemoteThread(IN HANDLE hProcess,
|
||||||
|
|
||||||
/* Create the Stack */
|
/* Create the Stack */
|
||||||
Status = BaseCreateStack(hProcess,
|
Status = BaseCreateStack(hProcess,
|
||||||
dwStackSize,
|
(dwCreationFlags & STACK_SIZE_PARAM_IS_A_RESERVATION) ?
|
||||||
dwCreationFlags & STACK_SIZE_PARAM_IS_A_RESERVATION ?
|
0 : dwStackSize,
|
||||||
dwStackSize : 0,
|
(dwCreationFlags & STACK_SIZE_PARAM_IS_A_RESERVATION) ?
|
||||||
|
dwStackSize : 0,
|
||||||
&InitialTeb);
|
&InitialTeb);
|
||||||
if (!NT_SUCCESS(Status))
|
if (!NT_SUCCESS(Status))
|
||||||
{
|
{
|
||||||
|
|
|
@ -346,22 +346,25 @@ BaseFormatObjectAttributes(OUT POBJECT_ATTRIBUTES ObjectAttributes,
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Creates a stack for a thread or fiber
|
* Creates a stack for a thread or fiber.
|
||||||
|
* NOTE: Adapted from sdk/lib/rtl/thread.c:RtlpCreateUserStack().
|
||||||
*/
|
*/
|
||||||
NTSTATUS
|
NTSTATUS
|
||||||
WINAPI
|
WINAPI
|
||||||
BaseCreateStack(HANDLE hProcess,
|
BaseCreateStack(
|
||||||
SIZE_T StackCommit,
|
_In_ HANDLE hProcess,
|
||||||
SIZE_T StackReserve,
|
_In_opt_ SIZE_T StackCommit,
|
||||||
PINITIAL_TEB InitialTeb)
|
_In_opt_ SIZE_T StackReserve,
|
||||||
|
_Out_ PINITIAL_TEB InitialTeb)
|
||||||
{
|
{
|
||||||
NTSTATUS Status;
|
NTSTATUS Status;
|
||||||
PIMAGE_NT_HEADERS Headers;
|
PIMAGE_NT_HEADERS Headers;
|
||||||
ULONG_PTR Stack;
|
ULONG_PTR Stack;
|
||||||
BOOLEAN UseGuard;
|
BOOLEAN UseGuard;
|
||||||
ULONG PageSize, Dummy, AllocationGranularity;
|
ULONG PageSize, AllocationGranularity, Dummy;
|
||||||
SIZE_T StackReserveHeader, StackCommitHeader, GuardPageSize, GuaranteedStackCommit;
|
SIZE_T MinimumStackCommit, GuardPageSize;
|
||||||
DPRINT("BaseCreateStack (hProcess: %p, Max: %lx, Current: %lx)\n",
|
|
||||||
|
DPRINT("BaseCreateStack(hProcess: 0x%p, Max: 0x%lx, Current: 0x%lx)\n",
|
||||||
hProcess, StackReserve, StackCommit);
|
hProcess, StackReserve, StackCommit);
|
||||||
|
|
||||||
/* Read page size */
|
/* Read page size */
|
||||||
|
@ -372,34 +375,38 @@ BaseCreateStack(HANDLE hProcess,
|
||||||
Headers = RtlImageNtHeader(NtCurrentPeb()->ImageBaseAddress);
|
Headers = RtlImageNtHeader(NtCurrentPeb()->ImageBaseAddress);
|
||||||
if (!Headers) return STATUS_INVALID_IMAGE_FORMAT;
|
if (!Headers) return STATUS_INVALID_IMAGE_FORMAT;
|
||||||
|
|
||||||
StackCommitHeader = Headers->OptionalHeader.SizeOfStackCommit;
|
if (StackReserve == 0)
|
||||||
StackReserveHeader = Headers->OptionalHeader.SizeOfStackReserve;
|
StackReserve = Headers->OptionalHeader.SizeOfStackReserve;
|
||||||
|
|
||||||
if (!StackReserve) StackReserve = StackReserveHeader;
|
if (StackCommit == 0)
|
||||||
|
|
||||||
if (!StackCommit)
|
|
||||||
{
|
{
|
||||||
StackCommit = StackCommitHeader;
|
StackCommit = Headers->OptionalHeader.SizeOfStackCommit;
|
||||||
}
|
}
|
||||||
|
/* Check if the commit is higher than the reserve */
|
||||||
else if (StackCommit >= StackReserve)
|
else if (StackCommit >= StackReserve)
|
||||||
{
|
{
|
||||||
|
/* Grow the reserve beyond the commit, up to 1MB alignment */
|
||||||
StackReserve = ROUND_UP(StackCommit, 1024 * 1024);
|
StackReserve = ROUND_UP(StackCommit, 1024 * 1024);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Align everything to Page Size */
|
||||||
StackCommit = ROUND_UP(StackCommit, PageSize);
|
StackCommit = ROUND_UP(StackCommit, PageSize);
|
||||||
StackReserve = ROUND_UP(StackReserve, AllocationGranularity);
|
StackReserve = ROUND_UP(StackReserve, AllocationGranularity);
|
||||||
|
|
||||||
GuaranteedStackCommit = NtCurrentTeb()->GuaranteedStackBytes;
|
MinimumStackCommit = NtCurrentPeb()->MinimumStackCommit;
|
||||||
if ((GuaranteedStackCommit) && (StackCommit < GuaranteedStackCommit))
|
if ((MinimumStackCommit != 0) && (StackCommit < MinimumStackCommit))
|
||||||
{
|
{
|
||||||
StackCommit = GuaranteedStackCommit;
|
StackCommit = MinimumStackCommit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Check if the commit is higher than the reserve */
|
||||||
if (StackCommit >= StackReserve)
|
if (StackCommit >= StackReserve)
|
||||||
{
|
{
|
||||||
|
/* Grow the reserve beyond the commit, up to 1MB alignment */
|
||||||
StackReserve = ROUND_UP(StackCommit, 1024 * 1024);
|
StackReserve = ROUND_UP(StackCommit, 1024 * 1024);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Align everything to Page Size */
|
||||||
StackCommit = ROUND_UP(StackCommit, PageSize);
|
StackCommit = ROUND_UP(StackCommit, PageSize);
|
||||||
StackReserve = ROUND_UP(StackReserve, AllocationGranularity);
|
StackReserve = ROUND_UP(StackReserve, AllocationGranularity);
|
||||||
|
|
||||||
|
@ -423,11 +430,11 @@ BaseCreateStack(HANDLE hProcess,
|
||||||
InitialTeb->PreviousStackBase = NULL;
|
InitialTeb->PreviousStackBase = NULL;
|
||||||
InitialTeb->PreviousStackLimit = NULL;
|
InitialTeb->PreviousStackLimit = NULL;
|
||||||
|
|
||||||
/* Update the Stack Position */
|
/* Update the stack position */
|
||||||
Stack += StackReserve - StackCommit;
|
Stack += StackReserve - StackCommit;
|
||||||
|
|
||||||
/* Check if we will need a guard page */
|
/* Check if we can add a guard page */
|
||||||
if (StackReserve > StackCommit)
|
if (StackReserve >= StackCommit + PageSize)
|
||||||
{
|
{
|
||||||
Stack -= PageSize;
|
Stack -= PageSize;
|
||||||
StackCommit += PageSize;
|
StackCommit += PageSize;
|
||||||
|
@ -456,11 +463,10 @@ BaseCreateStack(HANDLE hProcess,
|
||||||
/* Now set the current Stack Limit */
|
/* Now set the current Stack Limit */
|
||||||
InitialTeb->StackLimit = (PVOID)Stack;
|
InitialTeb->StackLimit = (PVOID)Stack;
|
||||||
|
|
||||||
/* Create a guard page */
|
/* Create a guard page if needed */
|
||||||
if (UseGuard)
|
if (UseGuard)
|
||||||
{
|
{
|
||||||
/* Set the guard page */
|
GuardPageSize = PageSize;
|
||||||
GuardPageSize = PAGE_SIZE;
|
|
||||||
Status = NtProtectVirtualMemory(hProcess,
|
Status = NtProtectVirtualMemory(hProcess,
|
||||||
(PVOID*)&Stack,
|
(PVOID*)&Stack,
|
||||||
&GuardPageSize,
|
&GuardPageSize,
|
||||||
|
@ -481,10 +487,14 @@ BaseCreateStack(HANDLE hProcess,
|
||||||
return STATUS_SUCCESS;
|
return STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NOTE: Adapted from sdk/lib/rtl/thread.c:RtlpFreeUserStack().
|
||||||
|
*/
|
||||||
VOID
|
VOID
|
||||||
WINAPI
|
WINAPI
|
||||||
BaseFreeThreadStack(IN HANDLE hProcess,
|
BaseFreeThreadStack(
|
||||||
IN PINITIAL_TEB InitialTeb)
|
_In_ HANDLE hProcess,
|
||||||
|
_In_ PINITIAL_TEB InitialTeb)
|
||||||
{
|
{
|
||||||
SIZE_T Dummy = 0;
|
SIZE_T Dummy = 0;
|
||||||
|
|
||||||
|
@ -501,10 +511,10 @@ BaseFreeThreadStack(IN HANDLE hProcess,
|
||||||
VOID
|
VOID
|
||||||
WINAPI
|
WINAPI
|
||||||
BaseInitializeContext(IN PCONTEXT Context,
|
BaseInitializeContext(IN PCONTEXT Context,
|
||||||
IN PVOID Parameter,
|
IN PVOID Parameter,
|
||||||
IN PVOID StartAddress,
|
IN PVOID StartAddress,
|
||||||
IN PVOID StackAddress,
|
IN PVOID StackAddress,
|
||||||
IN ULONG ContextType)
|
IN ULONG ContextType)
|
||||||
{
|
{
|
||||||
#ifdef _M_IX86
|
#ifdef _M_IX86
|
||||||
ULONG ContextFlags;
|
ULONG ContextFlags;
|
||||||
|
|
|
@ -183,10 +183,17 @@ BaseFormatObjectAttributes(OUT POBJECT_ATTRIBUTES ObjectAttributes,
|
||||||
|
|
||||||
NTSTATUS
|
NTSTATUS
|
||||||
WINAPI
|
WINAPI
|
||||||
BaseCreateStack(HANDLE hProcess,
|
BaseCreateStack(
|
||||||
SIZE_T StackReserve,
|
_In_ HANDLE hProcess,
|
||||||
SIZE_T StackCommit,
|
_In_opt_ SIZE_T StackCommit,
|
||||||
PINITIAL_TEB InitialTeb);
|
_In_opt_ SIZE_T StackReserve,
|
||||||
|
_Out_ PINITIAL_TEB InitialTeb);
|
||||||
|
|
||||||
|
VOID
|
||||||
|
WINAPI
|
||||||
|
BaseFreeThreadStack(
|
||||||
|
_In_ HANDLE hProcess,
|
||||||
|
_In_ PINITIAL_TEB InitialTeb);
|
||||||
|
|
||||||
VOID
|
VOID
|
||||||
WINAPI
|
WINAPI
|
||||||
|
@ -233,11 +240,6 @@ WINAPI
|
||||||
BaseThreadStartup(LPTHREAD_START_ROUTINE lpStartAddress,
|
BaseThreadStartup(LPTHREAD_START_ROUTINE lpStartAddress,
|
||||||
LPVOID lpParameter);
|
LPVOID lpParameter);
|
||||||
|
|
||||||
VOID
|
|
||||||
WINAPI
|
|
||||||
BaseFreeThreadStack(IN HANDLE hProcess,
|
|
||||||
IN PINITIAL_TEB InitialTeb);
|
|
||||||
|
|
||||||
__declspec(noreturn)
|
__declspec(noreturn)
|
||||||
VOID
|
VOID
|
||||||
WINAPI
|
WINAPI
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
NTSTATUS
|
NTSTATUS
|
||||||
NTAPI
|
NTAPI
|
||||||
RtlpCreateUserStack(IN HANDLE hProcess,
|
RtlpCreateUserStack(IN HANDLE ProcessHandle,
|
||||||
IN SIZE_T StackReserve OPTIONAL,
|
IN SIZE_T StackReserve OPTIONAL,
|
||||||
IN SIZE_T StackCommit OPTIONAL,
|
IN SIZE_T StackCommit OPTIONAL,
|
||||||
IN ULONG StackZeroBits OPTIONAL,
|
IN ULONG StackZeroBits OPTIONAL,
|
||||||
|
@ -29,10 +29,10 @@ RtlpCreateUserStack(IN HANDLE hProcess,
|
||||||
NTSTATUS Status;
|
NTSTATUS Status;
|
||||||
SYSTEM_BASIC_INFORMATION SystemBasicInfo;
|
SYSTEM_BASIC_INFORMATION SystemBasicInfo;
|
||||||
PIMAGE_NT_HEADERS Headers;
|
PIMAGE_NT_HEADERS Headers;
|
||||||
ULONG_PTR Stack = 0;
|
ULONG_PTR Stack;
|
||||||
BOOLEAN UseGuard = FALSE;
|
BOOLEAN UseGuard;
|
||||||
ULONG Dummy;
|
ULONG Dummy;
|
||||||
SIZE_T GuardPageSize;
|
SIZE_T MinimumStackCommit, GuardPageSize;
|
||||||
|
|
||||||
/* Get some memory information */
|
/* Get some memory information */
|
||||||
Status = ZwQuerySystemInformation(SystemBasicInformation,
|
Status = ZwQuerySystemInformation(SystemBasicInformation,
|
||||||
|
@ -42,26 +42,34 @@ RtlpCreateUserStack(IN HANDLE hProcess,
|
||||||
if (!NT_SUCCESS(Status)) return Status;
|
if (!NT_SUCCESS(Status)) return Status;
|
||||||
|
|
||||||
/* Use the Image Settings if we are dealing with the current Process */
|
/* Use the Image Settings if we are dealing with the current Process */
|
||||||
if (hProcess == NtCurrentProcess())
|
if (ProcessHandle == NtCurrentProcess())
|
||||||
{
|
{
|
||||||
/* Get the Image Headers */
|
/* Get the Image Headers */
|
||||||
Headers = RtlImageNtHeader(NtCurrentPeb()->ImageBaseAddress);
|
Headers = RtlImageNtHeader(NtCurrentPeb()->ImageBaseAddress);
|
||||||
if (!Headers) return STATUS_INVALID_IMAGE_FORMAT;
|
if (!Headers) return STATUS_INVALID_IMAGE_FORMAT;
|
||||||
|
|
||||||
/* If we didn't get the parameters, find them ourselves */
|
/* If we didn't get the parameters, find them ourselves */
|
||||||
if (!StackReserve) StackReserve = Headers->OptionalHeader.
|
if (StackReserve == 0)
|
||||||
SizeOfStackReserve;
|
StackReserve = Headers->OptionalHeader.SizeOfStackReserve;
|
||||||
if (!StackCommit) StackCommit = Headers->OptionalHeader.
|
if (StackCommit == 0)
|
||||||
SizeOfStackCommit;
|
StackCommit = Headers->OptionalHeader.SizeOfStackCommit;
|
||||||
|
|
||||||
|
MinimumStackCommit = NtCurrentPeb()->MinimumStackCommit;
|
||||||
|
if ((MinimumStackCommit != 0) && (StackCommit < MinimumStackCommit))
|
||||||
|
{
|
||||||
|
StackCommit = MinimumStackCommit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* Use the System Settings if needed */
|
/* Use the System Settings if needed */
|
||||||
if (!StackReserve) StackReserve = SystemBasicInfo.AllocationGranularity;
|
if (StackReserve == 0)
|
||||||
if (!StackCommit) StackCommit = SystemBasicInfo.PageSize;
|
StackReserve = SystemBasicInfo.AllocationGranularity;
|
||||||
|
if (StackCommit == 0)
|
||||||
|
StackCommit = SystemBasicInfo.PageSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check if the commit is higher than the reserve*/
|
/* Check if the commit is higher than the reserve */
|
||||||
if (StackCommit >= StackReserve)
|
if (StackCommit >= StackReserve)
|
||||||
{
|
{
|
||||||
/* Grow the reserve beyond the commit, up to 1MB alignment */
|
/* Grow the reserve beyond the commit, up to 1MB alignment */
|
||||||
|
@ -69,11 +77,12 @@ RtlpCreateUserStack(IN HANDLE hProcess,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Align everything to Page Size */
|
/* Align everything to Page Size */
|
||||||
StackReserve = ROUND_UP(StackReserve, SystemBasicInfo.AllocationGranularity);
|
|
||||||
StackCommit = ROUND_UP(StackCommit, SystemBasicInfo.PageSize);
|
StackCommit = ROUND_UP(StackCommit, SystemBasicInfo.PageSize);
|
||||||
|
StackReserve = ROUND_UP(StackReserve, SystemBasicInfo.AllocationGranularity);
|
||||||
|
|
||||||
/* Reserve memory for the stack */
|
/* Reserve memory for the stack */
|
||||||
Status = ZwAllocateVirtualMemory(hProcess,
|
Stack = 0;
|
||||||
|
Status = ZwAllocateVirtualMemory(ProcessHandle,
|
||||||
(PVOID*)&Stack,
|
(PVOID*)&Stack,
|
||||||
StackZeroBits,
|
StackZeroBits,
|
||||||
&StackReserve,
|
&StackReserve,
|
||||||
|
@ -82,41 +91,48 @@ RtlpCreateUserStack(IN HANDLE hProcess,
|
||||||
if (!NT_SUCCESS(Status)) return Status;
|
if (!NT_SUCCESS(Status)) return Status;
|
||||||
|
|
||||||
/* Now set up some basic Initial TEB Parameters */
|
/* Now set up some basic Initial TEB Parameters */
|
||||||
InitialTeb->PreviousStackBase = NULL;
|
|
||||||
InitialTeb->PreviousStackLimit = NULL;
|
|
||||||
InitialTeb->AllocatedStackBase = (PVOID)Stack;
|
InitialTeb->AllocatedStackBase = (PVOID)Stack;
|
||||||
InitialTeb->StackBase = (PVOID)(Stack + StackReserve);
|
InitialTeb->StackBase = (PVOID)(Stack + StackReserve);
|
||||||
|
InitialTeb->PreviousStackBase = NULL;
|
||||||
|
InitialTeb->PreviousStackLimit = NULL;
|
||||||
|
|
||||||
/* Update the Stack Position */
|
/* Update the stack position */
|
||||||
Stack += StackReserve - StackCommit;
|
Stack += StackReserve - StackCommit;
|
||||||
|
|
||||||
/* Check if we will need a guard page */
|
/* Check if we can add a guard page */
|
||||||
if (StackReserve > StackCommit)
|
if (StackReserve >= StackCommit + SystemBasicInfo.PageSize)
|
||||||
{
|
{
|
||||||
/* Remove a page to set as guard page */
|
|
||||||
Stack -= SystemBasicInfo.PageSize;
|
Stack -= SystemBasicInfo.PageSize;
|
||||||
StackCommit += SystemBasicInfo.PageSize;
|
StackCommit += SystemBasicInfo.PageSize;
|
||||||
UseGuard = TRUE;
|
UseGuard = TRUE;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UseGuard = FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
/* Allocate memory for the stack */
|
/* Allocate memory for the stack */
|
||||||
Status = ZwAllocateVirtualMemory(hProcess,
|
Status = ZwAllocateVirtualMemory(ProcessHandle,
|
||||||
(PVOID*)&Stack,
|
(PVOID*)&Stack,
|
||||||
0,
|
0,
|
||||||
&StackCommit,
|
&StackCommit,
|
||||||
MEM_COMMIT,
|
MEM_COMMIT,
|
||||||
PAGE_READWRITE);
|
PAGE_READWRITE);
|
||||||
if (!NT_SUCCESS(Status)) return Status;
|
if (!NT_SUCCESS(Status))
|
||||||
|
{
|
||||||
|
GuardPageSize = 0;
|
||||||
|
ZwFreeVirtualMemory(ProcessHandle, (PVOID*)&Stack, &GuardPageSize, MEM_RELEASE);
|
||||||
|
return Status;
|
||||||
|
}
|
||||||
|
|
||||||
/* Now set the current Stack Limit */
|
/* Now set the current Stack Limit */
|
||||||
InitialTeb->StackLimit = (PVOID)Stack;
|
InitialTeb->StackLimit = (PVOID)Stack;
|
||||||
|
|
||||||
/* Create a guard page */
|
/* Create a guard page if needed */
|
||||||
if (UseGuard)
|
if (UseGuard)
|
||||||
{
|
{
|
||||||
/* Attempt maximum space possible */
|
|
||||||
GuardPageSize = SystemBasicInfo.PageSize;
|
GuardPageSize = SystemBasicInfo.PageSize;
|
||||||
Status = ZwProtectVirtualMemory(hProcess,
|
Status = ZwProtectVirtualMemory(ProcessHandle,
|
||||||
(PVOID*)&Stack,
|
(PVOID*)&Stack,
|
||||||
&GuardPageSize,
|
&GuardPageSize,
|
||||||
PAGE_GUARD | PAGE_READWRITE,
|
PAGE_GUARD | PAGE_READWRITE,
|
||||||
|
@ -132,23 +148,21 @@ RtlpCreateUserStack(IN HANDLE hProcess,
|
||||||
return STATUS_SUCCESS;
|
return STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
NTSTATUS
|
VOID
|
||||||
NTAPI
|
NTAPI
|
||||||
RtlpFreeUserStack(IN HANDLE Process,
|
RtlpFreeUserStack(IN HANDLE ProcessHandle,
|
||||||
IN PINITIAL_TEB InitialTeb)
|
IN PINITIAL_TEB InitialTeb)
|
||||||
{
|
{
|
||||||
SIZE_T Dummy = 0;
|
SIZE_T Dummy = 0;
|
||||||
NTSTATUS Status;
|
|
||||||
|
|
||||||
/* Free the Stack */
|
/* Free the Stack */
|
||||||
Status = ZwFreeVirtualMemory(Process,
|
ZwFreeVirtualMemory(ProcessHandle,
|
||||||
&InitialTeb->AllocatedStackBase,
|
&InitialTeb->AllocatedStackBase,
|
||||||
&Dummy,
|
&Dummy,
|
||||||
MEM_RELEASE);
|
MEM_RELEASE);
|
||||||
|
|
||||||
/* Clear the initial TEB */
|
/* Clear the initial TEB */
|
||||||
RtlZeroMemory(InitialTeb, sizeof(INITIAL_TEB));
|
RtlZeroMemory(InitialTeb, sizeof(*InitialTeb));
|
||||||
return Status;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* FUNCTIONS ***************************************************************/
|
/* FUNCTIONS ***************************************************************/
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue