C++/VB - strcpy_s really safe?

Asked By richard
15-Feb-07 10:21 AM
I get a fatal error in both debug and release mode using strcpy_s function
with the following code.
Same problem in VS2005 and VS2005 SP1


char sTest[5];
strcpy_s(sTest,5,"0123456789");

I think it should in this case only copy the 4th first characters together
with a '\0' character at the end...but having my application crashing is
quirte unexpected!
STest
(1)
_SIZE
(1)
_DEST
(1)
StrSource
(1)
_RETURN_BUFFER_TOO_SMALL
(1)
DncDtYoijDEnYnZ
(1)
_CHAR
(1)
_SRC
(1)
  P.J. Plauger replied...
15-Feb-07 11:58 AM
strcpy_s in this case should report a runtime constraint violation,
which may be the "crash" you're witnessing. If the constraint handler
returns, the function stores a null character at sTest[0] and returns
a nonzero value.

At least that's the behavior required by TR24731, and I think Microsoft
conforms in this regard.

P.J. Plauger
Dinkumware, Ltd.
http://www.dinkumware.com
  richard replied...
15-Feb-07 12:39 PM
The strcpy_s doens't returns anything, it just crashes
This function is quite easy to understand, it copies bytes by bytes as long
it doesn't overlap.
In case of buffer overlap, that is the case in my sample, it should return
something, at least a return value ....
I never saw so far a function that voluntarily make the program crash with a
fatal error!
This is not an exception, it cannot be even catched by try method

strcpy_s function :

_FUNC_PROLOGUE
errno_t __cdecl _FUNC_NAME(_CHAR *_DEST, size_t _SIZE, const _CHAR *_SRC)
{
_CHAR *p;
size_t available;

/* validation section */
_VALIDATE_STRING(_DEST, _SIZE);
_VALIDATE_POINTER_RESET_STRING(_SRC, _DEST, _SIZE);

p = _DEST;
available = _SIZE;
while ((*p++ = *_SRC++) != 0 && --available > 0)
{
}

if (available == 0)
{
_RESET_STRING(_DEST, _SIZE);
_RETURN_BUFFER_TOO_SMALL(_DEST, _SIZE);
}
_FILL_STRING(_DEST, _SIZE, _SIZE - available + 1);
_RETURN_NO_ERROR;
}


OO2dncDtYoijDEnYnZ2dnUVZ_rGinZ2d@giganews.com...
  Arnie replied...
15-Feb-07 03:26 PM
Tou're misunderstanding how it's supposed to work.  Check the VS 2005
documentation for strcpy_s.  Especially the part that says:

If strDestination or strSource is a null pointer, or if the destination
string is too small, the invalid parameter handler is invoked as described
in Parameter Validation. If execution is allowed to continue, these
functions return EINVAL and set errno to EINVAL.
- Arnie
  Tamas Demjen replied...
15-Feb-07 05:41 PM
The behavior that you're looking for can be implemented using strncpy.
Note, however, that strncpy won't NUL terminate your string if it runs
out of the buffer. Take a look at this:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclib/html/_crt_strncpy.2c_.wcsncpy.2c_._mbsncpy.asp

The strncpy function copies the initial count characters of strSource to
strDest and returns strDest. If count is less than or equal to the
length of strSource, a null character is not appended automatically to
the copied string. If count is greater than the length of strSource, the
destination string is padded with null characters up to length count.

What Microsoft calls a null character is actually a NUL (the '\0').

What you want to achieve can be done this way:

char sTest[5];
strncpy(sTest, "0123456789", 5);
sTest[4] = '\0'; // <- yes, this is necessary too!

Finally, there's also an strncpy_s function.

Tom
  Tim Roberts replied...
16-Feb-07 01:24 AM
No, it shouldn't.  strcpy_s is merely an implementation of strcpy, and the
ISO definition of strcpy says that the behavior when the strings overlap is
undefined.  That means it can do anything it wants, including crashing the
program.

What are you expecting it to do?


No, but it can certainly be detected before calling the function.  If you
need to strcpy overlapping strings, you'll need to do this:

int x = strlen(src);
memmove( dst, src, x+1 );
--
Tim Roberts, timr@probo.com
Providenza & Boekelheide, Inc.
  Ulrich Eckhardt replied...
16-Feb-07 03:26 AM
Firstly, did you read and understand what this 'constraint handler' is? Now,
two things:
1. It's not strcpy_s that makes your program not work but it is your code.
You are simply supplying invalid parameters that, if used with strcpy,
would have invoked undefined behaviour but are caught by strcpy_s. It
doesn't change the fact that your code is buggy though.

2. When strcpy_s detects that the calling code is buggy (as it is the case
in your example), what should it do? Should it just do "something" and then
return to the caller, possibly facing buffer overruns and the resulting
exploits? No, when it detects this, it simply aborts the process, because
when the process is already in an inconsistent state, there is nothing left
it could do.

Without looking at TR24731, I guess that it first calls the constraint
handler, which still gives you a way to customise what happens.


Ah well, how many people check the returnvalue of functions that "obviously
can't fail" because "no user will ever enter something that is longer than
200 characters".


Then take a look at the assert() macro. Aborting when nothing is left to be
saved is the only sane way to handle such errors.


An exception in C? Or are you using C++, then I wonder why you aren't using
std::string anyway.


Uli
  richard replied...
16-Feb-07 04:08 AM
Thanks for your help tamas demjen, tim roberts and Ulrich, indeed your are
all right.
I simply misunderstood the function
help
protected data protected: ContainedType* _v; / / contents ContainedType _buffer[Dim]; / / buffer Index _dimension; / / total available Index _size; / / actual use / / / protected member functions protected: virtual ostream& _txtoutput(std::ostream& os) const { os<<_size<<std::endl; for(int it = 0;it! = _size;++it) os<<_v[it]<<" "; os<<endl; return os; } virtual istream& _txtinput(std::istream& is) { int dim; is> > dim; _redimension(dim); _size = dim; for(int it = 0;it! = dim;++it) { is> > _v[it]; } return is; } void _redimension(int newdimension) { assert(!(newdimension<0)); if(newdimension> Dim) { ContainedType* temp; temp = new ContainedType[newdimension]; _size = min(_size, newdimension); for(int it = 0;it! = _size;++it) temp[it] = _v[it]; if(_v! = _buffer)delete[] _v; _v = temp; for(int it _size;it! = newdimension;++it) _v[it] = ContainedType(); } else { _dimension = newdimension; _size = min(_size, newdimension); } } template<int
always failed with E_INVALIDARG? C++ / VB See following simple wrapper: [code:C++] class bstr_1D_safe_array { size_t _size; SAFEARRAY* _sa; public: bstr_1D_safe_array(size_t size) : _size(size) { SAFEARRAYBOUND sab[1]; sab[0].lLbound = 0; sab[0].cElements = _size; _sa = SafeArrayCreate(VT_BSTR, 1, sab); if (!_sa) throw std::exception("fail to call SafeArrayCreate"); } ~bstr_1D_safe_array SafeArrayDestroy(_sa); } size_t size() const { return _size; } void set_value(size_t index, const BSTR& value) { assert(index < _size); BSTR* pvalue = NULL; HRESULT hr = SafeArrayAccessData(_sa, (void HUGEP* *)pvalue); if (FAILED(hr)) throw std SafeArrayAccessData, always, failed, with, E_INVALIDARG? description: See following simple wrapper: [code:C++] class bstr_1D_safe_array { size_t _size; SAFEARRAY* _sa; public: bstr_1D_safe_array(size_t size) : _size(size)
OMTS (1) VbCrLf (1) ReUrl.Execute (1) ReUrl.Pattern (1) ReUrl.Global (1) FindUrls (1) STest (1) Gabriela schrieb: [. . .] Dim sTest : sTest = Join( Array( _ , "the regex should find ""http: / / ads.domain.ext"" but not ""http: / / sad whether and where" _ , "more tinkering with the pattern is needed." _ ), vbCrLf ) WScript.Echo sTest Dim reUrl : Set reUrl = New RegExp reUrl.Global = True reUrl.IgnoreCase = True reUrl.Pattern = """(http: / / ads \ .[^""]+)""" Dim oMTS : Set oMTS = reUrl.Execute( sTest ) Dim oMT For Each oMT In oMTS WScript.Echo oMT.SubMatches( 0 ) Next output: = = = findUrls