C:\sources>perl w32jobobj.pl 50 100 Opened process PID 14332Success, Perl assembled the 52428812 bytes long string. It will now be printed to STDOUT. 2 kernel handles were leaked in this code. This is a good time to hit Ctrl-C instead of hitting Spacebar or Enter.Press any key to continue . . . #### C:\sources>perl w32jobobj.pl 110 100 Out of memory! Opened process PID 7844 C:\sources> #### # this script leaks 2 kernel handles, fix that before putting into production use Win32::API; use warnings; use strict; BEGIN { sub PROCESS_TERMINATE { return 0x0001; } sub PROCESS_DUP_HANDLE {return 0x0040; } sub PROCESS_SET_QUOTA {return 0x0100; } sub FALSE {return 0; } sub INVALID_HANDLE_VALUE { return -1; } sub JobObjectExtendedLimitInformation { return 9;} sub JOB_OBJECT_QUERY { return 0x0004; } sub JOB_OBJECT_LIMIT_WORKINGSET { return 0x00000001; } sub JOB_OBJECT_LIMIT_PROCESS_TIME { return 0x00000002; } sub JOB_OBJECT_LIMIT_JOB_TIME { return 0x00000004; } sub JOB_OBJECT_LIMIT_ACTIVE_PROCESS { return 0x00000008; } sub JOB_OBJECT_LIMIT_AFFINITY { return 0x00000010; } sub JOB_OBJECT_LIMIT_PRIORITY_CLASS { return 0x00000020; } sub JOB_OBJECT_LIMIT_PRESERVE_JOB_TIME { return 0x00000040; } sub JOB_OBJECT_LIMIT_SCHEDULING_CLASS { return 0x00000080; } sub JOB_OBJECT_LIMIT_PROCESS_MEMORY { return 0x00000100; } sub JOB_OBJECT_LIMIT_JOB_MEMORY { return 0x00000200; } sub JOB_OBJECT_LIMIT_JOB_MEMORY_HIGH { return JOB_OBJECT_LIMIT_JOB_MEMORY; } sub JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION { return 0x00000400; } sub JOB_OBJECT_LIMIT_BREAKAWAY_OK { return 0x00000800; } sub JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK { return 0x00001000; } sub JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE { return 0x00002000; } sub JOB_OBJECT_LIMIT_SUBSET_AFFINITY { return 0x00004000; } sub JOB_OBJECT_LIMIT_JOB_MEMORY_LOW { return 0x00008000; } sub JOB_OBJECT_LIMIT_JOB_READ_BYTES { return 0x00010000; } sub JOB_OBJECT_LIMIT_JOB_WRITE_BYTES { return 0x00020000; } sub JOB_OBJECT_LIMIT_RATE_CONTROL { return 0x00040000; } sub JOB_OBJECT_LIMIT_CPU_RATE_CONTROL{ return JOB_OBJECT_LIMIT_RATE_CONTROL; } sub JOB_OBJECT_LIMIT_IO_RATE_CONTROL { return 0x00080000; } sub JOB_OBJECT_LIMIT_NET_RATE_CONTROL { return 0x00100000; } sub JOB_OBJECT_LIMIT_VALID_FLAGS { return 0x0007ffff; } sub DUPLICATE_SAME_ACCESS { return 0x00000002; } } die "Usage: perl win32_memleak_rsrc_limits.pl len_in_MBs_build_test_string RAM_quota_limit_in_MBs\n" if scalar(@ARGV) != 2 || !defined($ARGV[0]) || !defined($ARGV[1]); my ($howManyMBsString, $howManyMBsProcessRAMLimit) = ($ARGV[0]+0, $ARGV[1]+0); Win32::API::Type->typedef( 'LARGE_INTEGER', 'unsigned __int64'); # I have no idea why the ::Type->typedef('ULONG_PTR', 64_bits is required by # uname = Win32 strawberry-perl 5.32.1.1 #1 Sun Jan 24 12:17:47 2021 i386 # archname = MSWin32-x86-multi-thread-64int # which has 32 bit ptrs, and 'ULONG_PTR' is a size_t/ptr sized type # so its 4 bytes on i386, and 8 bytes on AMD64, but i386 strawberry or # the 32-bit edition of kernel32.dll want it as 64b inside a 32 bit process Win32::API::Type->typedef('ULONG_PTR', 'unsigned __int64'); # This is the correct typedef for ULONG_PTR, which is defined by MS as an # unsigned pointer sized integer, aka "void *" aka "size_t". The HANDLE # typedef works works in AMD64 Win64 Perl as expected: # Win32::API::Type->typedef('ULONG_PTR', 'HANDLE'); # but on Strawberry 5.32 i386 on Win7 AMD64, it fails with # $SetInformationJobObject->Call() GLR=87 at w32jobobj.pl line 167. # so 'ULONG_PTR' == 'unsigned __int64' is needed on i386 for this demo # script on i386 Perls but it makes no sense, there is a bug somewhere. # ~~~ bulk88 Win32::API::Type->typedef('JOBOBJECTINFOCLASS', 'int'); # C type enum aka I32 my $OpenProcess = Win32::API::More->new( 'kernel32', 'HANDLE OpenProcess( DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId)'); if (!$OpenProcess) { die 'OpenProcess GLR='.Win32::GetLastError(); } my $dwPID = $$; my $hProcess = $OpenProcess->Call(PROCESS_SET_QUOTA|PROCESS_TERMINATE|PROCESS_DUP_HANDLE, FALSE, $dwPID); if (! $hProcess || $hProcess == INVALID_HANDLE_VALUE) { printf("Could not open process PID %d, GLR=%d\n", $dwPID, Win32::GetLastError()); exit 1; } else { printf("Opened process PID %d", $dwPID); } typedef Win32::API::Struct 'JOBOBJECT_BASIC_LIMIT_INFORMATION' => qw( LARGE_INTEGER PerProcessUserTimeLimit; LARGE_INTEGER PerJobUserTimeLimit; DWORD LimitFlags; SIZE_T MinimumWorkingSetSize; SIZE_T MaximumWorkingSetSize; DWORD ActiveProcessLimit; ULONG_PTR Affinity; DWORD PriorityClass; DWORD SchedulingClass; ); typedef Win32::API::Struct 'IO_COUNTERS' => qw( ULONGLONG ReadOperationCount; ULONGLONG WriteOperationCount; ULONGLONG OtherOperationCount; ULONGLONG ReadTransferCount; ULONGLONG WriteTransferCount; ULONGLONG OtherTransferCount; ); Win32::API::Struct->typedef( 'JOBOBJECT_EXTENDED_LIMIT_INFORMATION' => qw( JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation; IO_COUNTERS IoInfo; SIZE_T ProcessMemoryLimit; SIZE_T JobMemoryLimit; SIZE_T PeakProcessMemoryUsed; SIZE_T PeakJobMemoryUsed; )); my $BasicLimitInformation = Win32::API::Struct->new('JOBOBJECT_BASIC_LIMIT_INFORMATION'); $BasicLimitInformation->{PerProcessUserTimeLimit} = 0; $BasicLimitInformation->{PerJobUserTimeLimit} = 0; $BasicLimitInformation->{LimitFlags} = 0; $BasicLimitInformation->{MinimumWorkingSetSize} = 0; $BasicLimitInformation->{MaximumWorkingSetSize} = 0; $BasicLimitInformation->{ActiveProcessLimit} = 0; $BasicLimitInformation->{Affinity} = 0; $BasicLimitInformation->{PriorityClass} = 0; $BasicLimitInformation->{SchedulingClass} = 0; my $IoInfo = Win32::API::Struct->new('IO_COUNTERS'); $IoInfo->{ReadOperationCount} = 0; $IoInfo->{WriteOperationCount} = 0; $IoInfo->{OtherOperationCount} = 0; $IoInfo->{ReadTransferCount} = 0; $IoInfo->{WriteTransferCount} = 0; $IoInfo->{OtherTransferCount} = 0; my $jelInfo = Win32::API::Struct->new('JOBOBJECT_EXTENDED_LIMIT_INFORMATION'); $jelInfo->{BasicLimitInformation} = $BasicLimitInformation; $jelInfo->{IoInfo} = $IoInfo; $jelInfo->{ProcessMemoryLimit} = 0; $jelInfo->{JobMemoryLimit} = 0; $jelInfo->{PeakProcessMemoryUsed} = 0; $jelInfo->{PeakJobMemoryUsed} = 0; #my $CreateJobObject = Win32::API::More->new( 'kernel32', # 'HANDLE CreateJobObjectA(LPSECURITY_ATTRIBUTES lpJobAttributes, LPCSTR lpName)'); my $CreateJobObject = Win32::API::More->new( 'kernel32', 'HANDLE CreateJobObjectA(HANDLE lpJobAttributes, HANDLE lpName)'); if(!$CreateJobObject) { die 'CreateJobObject GLR=' . Win32::GetLastError();} my $hJob = $CreateJobObject->Call(0,0); # XXX $hJob HANDLE is LEAKED XXX my( $dwProcessLimit, $dwJobMemory, $dwProcessMemory, $bKillProcOnJobClose, $bBreakAwayOK, $bSilentBreakAwayOK); $dwJobMemory = $howManyMBsProcessRAMLimit * 1024 * 1024; if ($dwProcessLimit){ $jelInfo->{BasicLimitInformation}->{LimitFlags} |= JOB_OBJECT_LIMIT_ACTIVE_PROCESS; $jelInfo->{BasicLimitInformation}->{ActiveProcessLimit} = $dwProcessLimit; } if ($dwJobMemory){ $jelInfo->{BasicLimitInformation}->{LimitFlags} |= JOB_OBJECT_LIMIT_JOB_MEMORY; $jelInfo->{JobMemoryLimit} = $dwJobMemory; } if ($dwProcessMemory) { $jelInfo->{BasicLimitInformation}->{LimitFlags} |= JOB_OBJECT_LIMIT_PROCESS_MEMORY; $jelInfo->{ProcessMemoryLimit} = $dwProcessMemory; } if ($bKillProcOnJobClose) { $jelInfo->{BasicLimitInformation}->{LimitFlags} |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; } if ($bBreakAwayOK) { $jelInfo->{BasicLimitInformation}->{LimitFlags} |= JOB_OBJECT_LIMIT_BREAKAWAY_OK; } if ($bSilentBreakAwayOK) { $jelInfo->{BasicLimitInformation}->{LimitFlags} |= JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK; } my $SetInformationJobObject = Win32::API::More->new( 'kernel32', 'BOOL SetInformationJobObject(HANDLE hJob,' .' JOBOBJECTINFOCLASS JobObjectInformationClass,' #.' LPVOID lpJobObjectInformation,' .' JOBOBJECT_EXTENDED_LIMIT_INFORMATION * lpJobObjectInformation,' .' DWORD cbJobObjectInformationLength)'); if (!$SetInformationJobObject) { die 'SetInformationJobObject GLR=' . Win32::GetLastError(); } my $b = $SetInformationJobObject->Call($hJob, JobObjectExtendedLimitInformation, $jelInfo, $jelInfo->sizeof()); if (!$b) { die '$SetInformationJobObject->Call() GLR=' . Win32::GetLastError(); } my $DuplicateHandle = Win32::API::More->new( 'kernel32', 'BOOL DuplicateHandle( HANDLE hSourceProcessHandle, HANDLE hSourceHandle, HANDLE hTargetProcessHandle, LPHANDLE lpTargetHandle, DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwOptions )'); if (!$DuplicateHandle) { die '$DuplicateHandle GLR=' . Win32::GetLastError();} my $GetCurrentProcess = Win32::API::More->new( 'kernel32', 'HANDLE GetCurrentProcess()'); if (!$GetCurrentProcess) { die '$GetCurrentProcess GLR=' . Win32::GetLastError();} my $hCurrentProcess = 0; # XXX this is LEAKED XXX $b = $DuplicateHandle->Call($GetCurrentProcess->Call(), $GetCurrentProcess->Call(), $GetCurrentProcess->Call(), $hCurrentProcess, 0, !!0, DUPLICATE_SAME_ACCESS); if (!$b) { die '$DuplicateHandle->Call() GLR=' . Win32::GetLastError(); } my $AssignProcessToJobObject = Win32::API::More->new( 'kernel32', 'BOOL AssignProcessToJobObject( HANDLE hJob, HANDLE hProcess)'); if (!$AssignProcessToJobObject) { die '$AssignProcessToJobObject GLR=' . Win32::GetLastError(); } # passing AssignProcessToJobObject() the psuedo-handle/perma-const from # GetCurrentProcess() makes it fail with 87 ERROR_INVALID_PARAMETER $b = $AssignProcessToJobObject->Call($hJob, $hCurrentProcess); if (!$b) { die '$AssignProcessToJobObject->Call() GLR=' . Win32::GetLastError(); } my $str = ''; my $tstr; while (length ($str) < ($howManyMBsString * 1024 * 1024)) { $tstr = sprintf('_123456789abcd_%016x',length($str)); $str .= $tstr; #print $tstr; } print "Success, Perl assembled the ".length($str)." bytes long string.\n" ."It will now be printed to STDOUT. " ."2 kernel handles were leaked in this code.\n" ."This is a good time to hit Ctrl-C instead of hitting Spacebar or Enter."; system 'pause'; print $str;