From ac0744d4505a583de2cbdc6082f2726393c797df Mon Sep 17 00:00:00 2001 From: Hans Leidekker Date: Tue, 7 Nov 2017 14:10:39 +0100 Subject: [PATCH] advapi32: Fix EnumServicesStatus on Wow64. The structures returned by this function contain pointers, which breaks on Wow64 if the client is 32-bit (the service manager always runs in a 64-bit process). This patch introduces a variant of ENUM_SERVICE_STATUS with offsets instead of pointers and converts the structures on the client side. The downside is that we need to buffer the data, but in return we can get rid of the dummy buffer pointer. Signed-off-by: Hans Leidekker Signed-off-by: Alexandre Julliard --- dlls/advapi32/service.c | 89 ++++++++++++++++++++++++++++++++++++----- include/wine/svcctl.idl | 8 ++++ programs/services/rpc.c | 19 +++++---- 3 files changed, 95 insertions(+), 21 deletions(-) diff --git a/dlls/advapi32/service.c b/dlls/advapi32/service.c index fa6ebfec317..79d069336ee 100644 --- a/dlls/advapi32/service.c +++ b/dlls/advapi32/service.c @@ -1654,6 +1654,17 @@ EnumServicesStatusA( SC_HANDLE hmngr, DWORD type, DWORD state, LPENUM_SERVICE_ST TRACE("%p 0x%x 0x%x %p %u %p %p %p\n", hmngr, type, state, services, size, needed, returned, resume_handle); + if (!hmngr) + { + SetLastError( ERROR_INVALID_HANDLE ); + return FALSE; + } + if (!needed || !returned) + { + SetLastError( ERROR_INVALID_ADDRESS ); + return FALSE; + } + sz = max( 2 * size, sizeof(*servicesW) ); if (!(servicesW = heap_alloc( sz ))) { @@ -1701,8 +1712,10 @@ EnumServicesStatusW( SC_HANDLE hmngr, DWORD type, DWORD state, LPENUM_SERVICE_ST services, DWORD size, LPDWORD needed, LPDWORD returned, LPDWORD resume_handle ) { - DWORD err, i; - ENUM_SERVICE_STATUSW dummy_status; + DWORD err, i, offset, buflen, count, total_size = 0; + struct enum_service_status *entry; + const WCHAR *str; + BYTE *buf; TRACE("%p 0x%x 0x%x %p %u %p %p %p\n", hmngr, type, state, services, size, needed, returned, resume_handle); @@ -1712,17 +1725,23 @@ EnumServicesStatusW( SC_HANDLE hmngr, DWORD type, DWORD state, LPENUM_SERVICE_ST SetLastError( ERROR_INVALID_HANDLE ); return FALSE; } + if (!needed || !returned) + { + SetLastError( ERROR_INVALID_ADDRESS ); + return FALSE; + } /* make sure we pass a valid pointer */ - if (!services || size < sizeof(*services)) + buflen = max( size, sizeof(*services) ); + if (!(buf = heap_alloc( buflen ))) { - services = &dummy_status; - size = sizeof(dummy_status); + SetLastError( ERROR_NOT_ENOUGH_MEMORY ); + return FALSE; } __TRY { - err = svcctl_EnumServicesStatusW( hmngr, type, state, (BYTE *)services, size, needed, returned, resume_handle ); + err = svcctl_EnumServicesStatusW( hmngr, type, state, buf, buflen, needed, &count, resume_handle ); } __EXCEPT(rpc_filter) { @@ -1730,20 +1749,68 @@ EnumServicesStatusW( SC_HANDLE hmngr, DWORD type, DWORD state, LPENUM_SERVICE_ST } __ENDTRY + *returned = 0; if (err != ERROR_SUCCESS) { + /* double the needed size to fit the potentially larger ENUM_SERVICE_STATUSW */ + if (err == ERROR_MORE_DATA) *needed *= 2; + heap_free( buf ); SetLastError( err ); return FALSE; } - for (i = 0; i < *returned; i++) + entry = (struct enum_service_status *)buf; + for (i = 0; i < count; i++) { - /* convert buffer offsets into pointers */ - services[i].lpServiceName = (WCHAR *)((char *)services + (DWORD_PTR)services[i].lpServiceName); - if (services[i].lpDisplayName) - services[i].lpDisplayName = (WCHAR *)((char *)services + (DWORD_PTR)services[i].lpDisplayName); + total_size += sizeof(*services); + if (entry->service_name) + { + str = (const WCHAR *)(buf + entry->service_name); + total_size += (strlenW( str ) + 1) * sizeof(WCHAR); + } + if (entry->display_name) + { + str = (const WCHAR *)(buf + entry->display_name); + total_size += (strlenW( str ) + 1) * sizeof(WCHAR); + } + entry++; } + if (total_size > size) + { + heap_free( buf ); + *needed = total_size; + SetLastError( ERROR_MORE_DATA ); + return FALSE; + } + + offset = count * sizeof(*services); + entry = (struct enum_service_status *)buf; + for (i = 0; i < count; i++) + { + DWORD str_size; + str = (const WCHAR *)(buf + entry->service_name); + str_size = (strlenW( str ) + 1) * sizeof(WCHAR); + services[i].lpServiceName = (WCHAR *)((char *)services + offset); + memcpy( services[i].lpServiceName, str, str_size ); + offset += str_size; + + if (!entry->display_name) services[i].lpDisplayName = NULL; + else + { + str = (const WCHAR *)(buf + entry->display_name); + str_size = (strlenW( str ) + 1) * sizeof(WCHAR); + services[i].lpDisplayName = (WCHAR *)((char *)services + offset); + memcpy( services[i].lpDisplayName, str, str_size ); + offset += str_size; + } + services[i].ServiceStatus = entry->service_status; + entry++; + } + + heap_free( buf ); + *needed = 0; + *returned = count; return TRUE; } diff --git a/include/wine/svcctl.idl b/include/wine/svcctl.idl index 9fd5ca4c79a..b7d1e927188 100644 --- a/include/wine/svcctl.idl +++ b/include/wine/svcctl.idl @@ -210,6 +210,14 @@ typedef enum _SC_ENUM_TYPE { cpp_quote("#endif") +/* internal version of ENUM_SERVICE_STATUSA/W that doesn't depend on pointer size */ +struct enum_service_status +{ + DWORD service_name; + DWORD display_name; + SERVICE_STATUS service_status; +}; + typedef struct _SERVICE_RPC_REQUIRED_PRIVILEGES_INFO { DWORD cbRequiredPrivileges; [size_is(cbRequiredPrivileges)] BYTE *pRequiredPrivileges; diff --git a/programs/services/rpc.c b/programs/services/rpc.c index 4435af935d7..7215504e824 100644 --- a/programs/services/rpc.c +++ b/programs/services/rpc.c @@ -1342,11 +1342,10 @@ DWORD __cdecl svcctl_EnumServicesStatusW( LPDWORD returned, LPDWORD resume) { - DWORD err, sz, total_size, num_services; - DWORD_PTR offset; + DWORD err, sz, total_size, num_services, offset; struct sc_manager_handle *manager; struct service_entry *service; - ENUM_SERVICE_STATUSW *s; + struct enum_service_status *s; WINE_TRACE("(%p, 0x%x, 0x%x, %p, %u, %p, %p, %p)\n", hmngr, type, state, buffer, size, needed, returned, resume); @@ -1366,7 +1365,7 @@ DWORD __cdecl svcctl_EnumServicesStatusW( { if ((service->status.dwServiceType & type) && map_state(service->status.dwCurrentState, state)) { - total_size += sizeof(ENUM_SERVICE_STATUSW); + total_size += sizeof(*s); total_size += (strlenW(service->name) + 1) * sizeof(WCHAR); if (service->config.lpDisplayName) { @@ -1382,26 +1381,26 @@ DWORD __cdecl svcctl_EnumServicesStatusW( scmdatabase_unlock(manager->db); return ERROR_MORE_DATA; } - s = (ENUM_SERVICE_STATUSW *)buffer; - offset = num_services * sizeof(ENUM_SERVICE_STATUSW); + s = (struct enum_service_status *)buffer; + offset = num_services * sizeof(struct enum_service_status); LIST_FOR_EACH_ENTRY(service, &manager->db->services, struct service_entry, entry) { if ((service->status.dwServiceType & type) && map_state(service->status.dwCurrentState, state)) { sz = (strlenW(service->name) + 1) * sizeof(WCHAR); memcpy(buffer + offset, service->name, sz); - s->lpServiceName = (WCHAR *)offset; /* store a buffer offset instead of a pointer */ + s->service_name = offset; offset += sz; - if (!service->config.lpDisplayName) s->lpDisplayName = NULL; + if (!service->config.lpDisplayName) s->display_name = 0; else { sz = (strlenW(service->config.lpDisplayName) + 1) * sizeof(WCHAR); memcpy(buffer + offset, service->config.lpDisplayName, sz); - s->lpDisplayName = (WCHAR *)offset; + s->display_name = offset; offset += sz; } - s->ServiceStatus = service->status; + s->service_status = service->status; s++; } }