Safe string copying
I'm doing some network programming, and I'd like you to review a small piece of code. I want to know if this is safe (regarding bufferoverflows and other security risks).
I'm getting an unsigned char* data and an unsigned int dataLength from a networking API. This is the data from the packet received. The data parameter SHOULD contain (in order):
a message type byte
a null-terminated account number string
a null-terminated password string.
..but ofcourse an attacker could put anything in there.
I'm also having a strange issue with the std::string. When I create it in one way, it works, and in another way it doesn't, see below.
Any other suggestions (like an alternative to the cast maybe?) are welcome too.
int f(unsigned char* data, unsigned int dataLength)
std::string acc(reinterpret_cast<char*>(data), 1, 20);
std::string pass(reinterpret_cast<char*>(data + acc.length() + 2), 0, 20); //works
//std::string pass(reinterpret_cast<char*>(data), acc.length() + 2, 20); //doesn't work
std::cout << acc << " " << pass << std::endl; // for testing only.
The code is unsafe.
std::string doesn't have a constructor that takes (char*, int, int), which is what you pass.
It has one that takes (char*, size_t), a data pointer and a length.
It has one that takes (std::string, size_t, size_t), an existing string, an offset and a length.
Because you pass three arguments, only the three-argument constructor is considered. Conversion of int to size_t is safe in this case. However, the char* is implicitly converted to a temporary std::string, and that's not only definitely unsafe (if the attacker sends no nul byte, the constructor will run off the buffer), it also doesn't work correctly, as you've found out.
Thanks, how about this:
I've changed the packet format a bit. The first byte is still the message type, then followed by 6 characters for the account number, followed by 1 to 20 characters for the password. So the minimum packet size is 8 bytes, and maximum 27 bytes.
int f(unsigned char* data, unsigned int dataLength, SystemID sourceSystem)
if(dataLength < 8 || dataLength > 27)
std::cout << "WARNING: corrupt LOGIN_REQUEST packet received from: \n"
<< "SystemID: " << sourceSystem << "\n"
<< "(IP:port): " << (net->getSystemAddress(sourceSystem)).ToString(true) << "\n"
<< (dataLength < 8 ? "packet too small" : "packet too large") << std::endl;
strncpy_s(buff, 21, reinterpret_cast<char*>(data + 1), 6);
strncpy_s(buff, 21, reinterpret_cast<char*>(data + 1 + 6), dataLength - 7);
// testing only:
std::cout << acc << " " << acc.length() << "\n" << pass << " " << pass.length()
<< " " << dataLength << std::endl;
Well, now it's unportable, but at least it's safe.
Shouldn't you return something different on error than on success?
Hmm, I guess the strncpy_s makes it non-portable? Do you have a suggestion for a portable and safe alternative?
The return value indicates what to do with the packet that fired the event. I want it to be deallocated in both cases, so this is intended.
strncpy() is reasonable portable (C99) and safe enough for this purpose. For the account number, which is of a fixed size, I'd just use memcpy() and manual termination.