Properly handle possible realloc failure
PATCH 1/1 Properly handle possible realloc failure
--- src/openvpnserv/interactive.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-)
src/openvpnserv/interactive.c +7 -3
@@ -1326,15 +1326,19 @@ UpdateWaitHandles (LPHANDLE *handles_ptr, LPDWORD count,if (pos == size) { size +=10 ; - handles =realloc (handles, size *sizeof (HANDLE )); -if (handles ==NULL ) +LPHANDLE tmp =realloc (handles, size *sizeof (HANDLE )); +if (tmp ==NULL )return ERROR_OUTOFMEMORY ; +else + { + handles = tmp; + *handles_ptr = handles; + } } handles[pos++] = threads->data; threads = threads->next; } - *handles_ptr = handles; *count = pos;return NO_ERROR ; }
Signature · 1 line
1.7.10.4
Quoted 4 lines · Selva Nair
--- src/openvpnserv/interactive.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-)
Please ignore this patch. It is broken -- patch made prematurely before a fixup was committed,
My apologies for the noise..
Selva
Quoted 11 lines · Selva Nair
Quoted 4 lines · Selva Nair
--- src/openvpnserv/interactive.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-)Please ignore this patch. It is broken -- patch made prematurely before a fixup was committed,
While redoing this patch I tested what actually happens on malloc or realloc failure and found a real bug. The patch in the next email addresses that.
Selva
PATCH 1/1 Fix handling of out of memory error in interactive service
Currently realloc failure in UpdateWaitHandles() is handled by triggering exit_event and waiting for all active worker threads to terminate. However, at this point the wait handles array will contain an invalid value (handle of the latest thread that is terminated), causing a cycle of WAIT_FAILED <-> continue and trashing of the eventlog.
Fix:
- Update the wait handles again after removing the last thread:
this should not fail as no extra memory is needed. Do not set the exit event; existing connections are not terminated.
- In case of WAIT_FAILED, break out of the while loop and exit instead of continue. This usually happens when one or more handles are invalid, which is hard to recover from.
Other changes:
- Use minimal initial allocation size so that the realloc code path gets exercised (2 or more connections will cause realloc).
- Use a temp variable to check the return value of realloc().
- Initialize handles array pointer to NULL.
Tested using a dummy realloc that returns NULL.
--- src/openvpnserv/interactive.c | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-)
src/openvpnserv/interactive.c +24 -11
@@ -1453,13 +1453,14 @@ static DWORD UpdateWaitHandles (LPHANDLE *handles_ptr, LPDWORD count, HANDLE io_event, HANDLE exit_event, list_item_t *threads) { -static DWORD size =10 ; +static DWORD size =2 ;static LPHANDLE handles =NULL ;DWORD pos =0 ;if (handles ==NULL ) { handles =malloc (size *sizeof (HANDLE )); + *handles_ptr = handles;if (handles ==NULL )return ERROR_OUTOFMEMORY ; }
@@ -1474,15 +1475,20 @@ UpdateWaitHandles (LPHANDLE *handles_ptr, LPDWORD count,if (pos == size) { size +=10 ; - handles =realloc (handles, size *sizeof (HANDLE )); -if (handles ==NULL ) -return ERROR_OUTOFMEMORY ; +LPHANDLE tmp =realloc (handles, size *sizeof (HANDLE )); +if (tmp ==NULL ) + { + size -=10 ; + *count = pos; +return ERROR_OUTOFMEMORY ; + } + handles = tmp; + *handles_ptr = handles; } handles[pos++] = threads->data; threads = threads->next; } - *handles_ptr = handles; *count = pos;return NO_ERROR ; }
@@ -1502,8 +1508,9 @@ ServiceStartInteractive (DWORD dwArgc, LPTSTR *lpszArgv)OVERLAPPED overlapped;DWORD error =NO_ERROR ;list_item_t *threads =NULL ; -PHANDLE handles; +PHANDLE handles =NULL ;DWORD handle_count; +BOOL CmpHandle (LPVOID item,LPVOID hnd) {return item == hnd; } service =RegisterServiceCtrlHandlerEx (interactive_service.name, ServiceCtrlInteractive, &status);if (!service)
@@ -1566,14 +1573,18 @@ ServiceStartInteractive (DWORD dwArgc, LPTSTR *lpszArgv)HANDLE thread =CreateThread (NULL ,0 , RunOpenvpn, pipe,CREATE_SUSPENDED ,NULL );if (thread) { - error =AddListItem (&threads, thread) || -UpdateWaitHandles (&handles, &handle_count, io_event, exit_event, threads); + error =AddListItem (&threads, thread); +if (!error) + error =UpdateWaitHandles (&handles, &handle_count, io_event, exit_event, threads);if (error) { +ReturnError (pipe, error,L"Insufficient reources to service new clients" ,1 , &exit_event); +/* Update wait handles again after removing the last worker thread */ +RemoveListItem (&threads, CmpHandle, thread); +UpdateWaitHandles (&handles, &handle_count, io_event, exit_event, threads);TerminateThread (thread,1 );CloseHandleEx (&thread);CloseHandleEx (&pipe); -SetEvent (exit_event); }else ResumeThread (thread);
@@ -1590,7 +1601,10 @@ ServiceStartInteractive (DWORD dwArgc, LPTSTR *lpszArgv)if (error ==WAIT_FAILED ) {MsgToEventLog (M_SYSERR ,TEXT ("WaitForMultipleObjects failed" )); -continue ; +SetEvent (exit_event); +/* Give some time for worker threads to exit and then terminate */ +Sleep (1000 ); +break ; }if (!threads) {
@@ -1602,7 +1616,6 @@ ServiceStartInteractive (DWORD dwArgc, LPTSTR *lpszArgv) }/* Worker thread ended */ -BOOL CmpHandle (LPVOID item,LPVOID hnd) {return item == hnd; }HANDLE thread =RemoveListItem (&threads, CmpHandle, handles[error]);UpdateWaitHandles (&handles, &handle_count, io_event, exit_event, threads);CloseHandleEx (&thread);
Signature · 1 line
1.7.10.4
Re: [Openvpn-devel] [PATCH] Fix handling of out of memory error in interactive service
Hi,
thanks for digging into this. Overall, the patch makes sense, I'm just wondering about this:
Quoted 3 lines · Selva Nair
Other changes:
- Use minimal initial allocation size so that the realloc code path gets exercised (2 or more connections will cause realloc).
I can see that this is useful for testing, but do we want this in production?
It won't make a difference for most usages ("only one connection active"), but then, the extra memory use for 10 is minimal, and realloc() *can* fail, so just keeping at 10 won't need the realloc() path generally...
Dunno, just thinking loud.
gert
Signature · 4 lines
USENET is *not* the non-clickable part of WWW!
//www.muc.de/~gert/
Gert Doering - Munich, Germany [email protected]...
fax: +49-89-35655025 [email protected]...Re: [Openvpn-devel] [PATCH] Fix handling of out of memory error in interactive service
Hi,
Thanks for the comments.
Quoted 8 lines · Gert Doering
thanks for digging into this. Overall, the patch makes sense, I'm just wondering about this:
Quoted 3 lines · Selva Nair
Other changes:
- Use minimal initial allocation size so that the realloc code path gets exercised (2 or more connections will cause realloc).
Quoted 8 lines
I can see that this is useful for testing, but do we want this in production?
It won't make a difference for most usages ("only one connection active"), but then, the extra memory use for 10 is minimal, and
realloc()*can* fail, so just keeping at 10 won't need therealloc()path generally...
Of course, the extra memory usage is not a concern. As a general practice I like malloc/realloc for growing arrays to start from the smallest possible value to expose bugs in the logic during normal use[*].
That said, I'm ok with reverting it back to 10 or even a somewhat larger value -- I understand the "in case the fix is buggy, be sure it doesn't break anything" kind of feeling.
Selva
[*] See similar advice in "The Practice of Programming", section on growing arrays..
Re: [Openvpn-devel] [PATCH] Fix handling of out of memory error in interactive service
In reply to Selva NairHi,
src/openvpnserv/interactive.c UpdateWaitHandles (LPHANDLE *handles_ptr, LPDWORD count, · line 1479
Quoted 7 lines · Selva Nair
{ size += 10;src/openvpnserv/interactive.c +2
-3- handles =realloc (handles, size *sizeof (HANDLE )); -if (handles ==NULL ) -return ERROR_OUTOFMEMORY ; +LPHANDLE tmp =realloc (handles, size *sizeof (HANDLE )); +if (tmp ==NULL )
... oh, and this might actually fail MSVC compilation, due to "variable declared after other code parts" (size += 10)...
src/openvpnserv/interactive.c ServiceStartInteractive (DWORD dwArgc, LPTSTR *lpszArgv) · line 1576
Quoted 10 lines
if (error) {src/openvpnserv/interactive.c +4
-1+ReturnError (pipe, error,L"Insufficient reources to service new clients" ,1 , &exit_event); +/* Update wait handles again after removing the last worker thread */ +RemoveListItem (&threads, CmpHandle, thread); +UpdateWaitHandles (&handles, &handle_count, io_event, exit_event, threads);TerminateThread (thread,1 );CloseHandleEx (&thread);CloseHandleEx (&pipe); -SetEvent (exit_event);
So we do not exit anymore when this fails, except if WAIT_FAILED?
gert
Signature · 4 lines
USENET is *not* the non-clickable part of WWW!
//www.muc.de/~gert/
Gert Doering - Munich, Germany [email protected]...
fax: +49-89-35655025 [email protected]...Re: [Openvpn-devel] [PATCH] Fix handling of out of memory error in interactive service
copy to the list...
Quoted 11 lines · Gert Doering
Hi,
src/openvpnserv/interactive.c UpdateWaitHandles (LPHANDLE *handles_ptr, LPDWORD count, · line 1479
Quoted 7 lines · Selva Nair
{ size += 10;src/openvpnserv/interactive.c +2
-3- handles =realloc (handles, size *sizeof (HANDLE )); -if (handles ==NULL ) -return ERROR_OUTOFMEMORY ; +LPHANDLE tmp =realloc (handles, size *sizeof (HANDLE )); +if (tmp ==NULL )
Quoted 3 lines
... oh, and this might actually fail MSVC compilation, due to "variable declared after other code parts" (size += 10)...
Isn't it time to deprecate support for visual studio older than 2013? MSVC in VS-2015 supports C99 constructs and VS2013 may also do.
If not, I'll submit a version 2.
Quoted 9 lines
Quoted 4 lines
if (error) {+ ReturnError (pipe, error, L"Insufficient reources to
service new clients", 1,
&exit_event);Quoted 1 line
+ /* Update wait handles again after removing the last
worker thread */
Review
Quoted 2 lines
(quoted diff) +2
-0+ RemoveListItem (&threads, CmpHandle, thread); + UpdateWaitHandles (&handles, &handle_count, io_event,
Quoted 8 lines
exit_event, threads);Quoted 4 lines
TerminateThread (thread, 1); CloseHandleEx (&thread); CloseHandleEx (&pipe);- SetEvent (
exit_event);So we do not exit anymore when this fails, except if
WAIT_FAILED?
That is correct. I tested it and works nicely -- i.e., on realloc error, all previous connections continue while new connections get an error message.
Selva
Re: [Openvpn-devel] [PATCH] Fix handling of out of memory error in interactive service
Hi,
Quoted 9 lines · Selva Nair
Quoted 9 lines · Selva Nair
Quoted 7 lines
{ size += 10;src/openvpnserv/interactive.c +2
-3- handles =realloc (handles, size *sizeof (HANDLE )); -if (handles ==NULL ) -return ERROR_OUTOFMEMORY ; +LPHANDLE tmp =realloc (handles, size *sizeof (HANDLE )); +if (tmp ==NULL )
Quoted 7 lines
Quoted 2 lines
... oh, and this might actually fail MSVC compilation, due to "variable declared after other code parts" (size += 10)...
Isn't it time to deprecate support for visual studio older than 2013? MSVC in VS-2015 supports C99 constructs and VS2013 may also do.
If not, I'll submit a version 2.
Dunno... the only time I compiled on windows, I was using mingw+msys... but since I get yelled at if we break MSVC compatibility, I started to look out for such constructs.
Lev, what are you compiling with?
[..]
Quoted 5 lines
Quoted 1 line
So we do not exit anymore when this fails, except if
WAIT_FAILED?That is correct. I tested it and works nicely -- i.e., on realloc error, all previous connections continue while new connections get an error message.
This is very nice indeed!
(Not that a realloc() failure for +40 bytes would ever happen on its own - if memory is that tight, other stuff will explode as well :-) - but I really really like such defensive code that just works with what it has, instead of "oh, panic, just crash")
Mmmh, anyway, I have two options now :-) - ask you for a v2 that is MSVC 2010 compatible, and has the initial value reverted to "10", or just apply...
gert
Signature · 4 lines
USENET is *not* the non-clickable part of WWW!
//www.muc.de/~gert/
Gert Doering - Munich, Germany [email protected]...
fax: +49-89-35655025 [email protected]...Re: [Openvpn-devel] [PATCH] Fix handling of out of memory error in interactive service
Quoted 4 lines · Gert Doering
Mmmh, anyway, I have two options now :-) - ask you for a v2 that is MSVC 2010 compatible, and has the initial value reverted to "10", or just apply...
Let's make life easy -- will do a v2 with both those changes:-) and just hope if/when we touch that realloc loop in future we'll test it by exercising it... I normally run 2 connections on my laptop (sometimes 3), never 10.
As a general rule, which MSVC version to target is still useful to know as there are a few places in the code requiring ugly scoping brackets as seen in some pending patches for MSVC compliance.
Selva
PATCH v2 1/1 Fix handling of out of memory error in interactive service
In reply to Selva NairCurrently realloc failure in UpdateWaitHandles() is handled by triggering exit_event and waiting for all active worker threads to terminate. However, at this point the wait handles array will contain an invalid value (handle of the latest thread that is terminated), causing a cycle of WAIT_FAILED <-> continue and trashing of the eventlog.
Fix:
- Update the wait handles again after removing the last thread:
this should not fail as no extra memory is needed. Do not set the exit event; existing connections are not terminated.
- In case of WAIT_FAILED, break out of the while loop and exit instead of continue. This usually happens when one or more handles are invalid, which is hard to recover from.
Other changes:
- Use minimal initial allocation size so that the realloc code path gets exercised (2 or more connections will cause realloc).
- Use a temp variable to check the return value of realloc().
- Initialize handles array pointer to NULL.
v2 changes:
- Increased initial allocation to 10 (warn: now 10 or more connections needed to exercise the realloc code path).
- Moved up the declaration of "LPHANDLE tmp" to please stone-age MSVC.
Tested using a dummy realloc that returns NULL.
--- src/openvpnserv/interactive.c | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-)
src/openvpnserv/interactive.c +24 -10
@@ -1460,6 +1460,7 @@ UpdateWaitHandles (LPHANDLE *handles_ptr, LPDWORD count,if (handles ==NULL ) { handles =malloc (size *sizeof (HANDLE )); + *handles_ptr = handles;if (handles ==NULL )return ERROR_OUTOFMEMORY ; }
@@ -1473,16 +1474,22 @@ UpdateWaitHandles (LPHANDLE *handles_ptr, LPDWORD count, {if (pos == size) { +LPHANDLE tmp; size +=10 ; - handles =realloc (handles, size *sizeof (HANDLE )); -if (handles ==NULL ) -return ERROR_OUTOFMEMORY ; + tmp =realloc (handles, size *sizeof (HANDLE )); +if (tmp ==NULL ) + { + size -=10 ; + *count = pos; +return ERROR_OUTOFMEMORY ; + } + handles = tmp; + *handles_ptr = handles; } handles[pos++] = threads->data; threads = threads->next; } - *handles_ptr = handles; *count = pos;return NO_ERROR ; }
@@ -1502,8 +1509,9 @@ ServiceStartInteractive (DWORD dwArgc, LPTSTR *lpszArgv)OVERLAPPED overlapped;DWORD error =NO_ERROR ;list_item_t *threads =NULL ; -PHANDLE handles; +PHANDLE handles =NULL ;DWORD handle_count; +BOOL CmpHandle (LPVOID item,LPVOID hnd) {return item == hnd; } service =RegisterServiceCtrlHandlerEx (interactive_service.name, ServiceCtrlInteractive, &status);if (!service)
@@ -1566,14 +1574,18 @@ ServiceStartInteractive (DWORD dwArgc, LPTSTR *lpszArgv)HANDLE thread =CreateThread (NULL ,0 , RunOpenvpn, pipe,CREATE_SUSPENDED ,NULL );if (thread) { - error =AddListItem (&threads, thread) || -UpdateWaitHandles (&handles, &handle_count, io_event, exit_event, threads); + error =AddListItem (&threads, thread); +if (!error) + error =UpdateWaitHandles (&handles, &handle_count, io_event, exit_event, threads);if (error) { +ReturnError (pipe, error,L"Insufficient resources to service new clients" ,1 , &exit_event); +/* Update wait handles again after removing the last worker thread */ +RemoveListItem (&threads, CmpHandle, thread); +UpdateWaitHandles (&handles, &handle_count, io_event, exit_event, threads);TerminateThread (thread,1 );CloseHandleEx (&thread);CloseHandleEx (&pipe); -SetEvent (exit_event); }else ResumeThread (thread);
@@ -1590,7 +1602,10 @@ ServiceStartInteractive (DWORD dwArgc, LPTSTR *lpszArgv)if (error ==WAIT_FAILED ) {MsgToEventLog (M_SYSERR ,TEXT ("WaitForMultipleObjects failed" )); -continue ; +SetEvent (exit_event); +/* Give some time for worker threads to exit and then terminate */ +Sleep (1000 ); +break ; }if (!threads) {
@@ -1602,7 +1617,6 @@ ServiceStartInteractive (DWORD dwArgc, LPTSTR *lpszArgv) }/* Worker thread ended */ -BOOL CmpHandle (LPVOID item,LPVOID hnd) {return item == hnd; }HANDLE thread =RemoveListItem (&threads, CmpHandle, handles[error]);UpdateWaitHandles (&handles, &handle_count, io_event, exit_event, threads);CloseHandleEx (&thread);
Signature · 1 line
1.7.10.4
[Openvpn-devel] [PATCH applied] Re: Fix handling of out of memory error in interactive service
ACK, thanks.
Your patch has been applied to the master branch.
commit 600dd9a16fc61ff6e595f500fba5daf14248b739
Author: Selva Nair Date: Sun May 22 14:39:32 2016 -0400
Fix handling of out of memory error in interactive service
Signed-off-by: Selva Nair <[email protected]...>
Acked-by: Gert Doering <[email protected]...>
Message-Id: <[email protected]...>
URL: http://article.gmane.org/gmane.network.openvpn.devel/11708
Signed-off-by: Gert Doering <[email protected]...>--
kind regards,
Gert Doering