Part VIIb: Security (concluded) of 64 Network DO’s and DON’Ts for Multi-Player Game Developers

 
Author:  Follow: TwitterFacebook
Job Title:Sarcastic Architect
Hobbies:Thinking Aloud, Arguing with Managers, Annoying HRs,
Calling a Spade a Spade, Keeping Tongue in Cheek
 
 

Today we’re concluding Part VII (Security), that in turn concludes our epic article on implementing network support for game engines. Yahoo! We’ve finally made it! 🙂

Security Backdoor

Previous parts:

Part I. Client Side
Part IIa. Protocols and APIs
Part IIb. Protocols and APIs (continued)
Part IIIa. Server-Side (Store-process-and-Forward Architecture)
Part IIIb: Server Side (deployment, optimizations, and testing)
Part IV: Great TCP vs UDP Debate
Part V. UDP
Part VI. TCP
Part VIIa. Security (TLS/SSL)

In the previous Part VIIa, we’ve discussed quite a few subtle issues related to TLS and OpenSSL. However, while TLS/SSL should normally be a cornerstone of your network security, taken alone it is not sufficient to protect your game and your players from attacks. Present Part VIIb will discuss security issues which are not related to TLS/SSL.

57. DO Remember about Buffer Overwrite (and Buffer Overread Too)

Out of all the security bugs in software, one has got significant attention, and has caused quite a few nasty hacks – it is buffer overwrites (a.k.a. buffer overflows, though, strictly speaking, “buffer overflows” cover not only buffer overwrites, but also buffer overreads, see below). If you write in C/C++, and have an on-stack buffer, and then copy a string, which you expect to be less than the buffer, but which comes over the network – you’re pretty much doomed:

void recv_and_parse( sock ) {
  char packet[256];
  ssize_t rcv = recv(sock,packet,256,0);
  if( rcv < 0 ) throw ...;
  char buf[16];
  //ASSUMPTION: we always have null-terminated string at the beginning of our packet
  strcpy(buf,packet); //SCREW-UP: !!!SECURITY PROBLEM – NEVER WRITE LIKE THIS!!!
  //...
}

NX bit The NX bit, which stands for No-eXecute, is a technology used in CPUs to segregate areas of memory for use by either storage of processor instructions (code) or for storage of data— Wikipedia —If you write it like in the line labeled “SCREW-UP” above, you can easily start writing beyond buf; moreover, a carefully crafted packet can overwrite return address on a program stack, so that when you return from your recv_and_parse() function, you return not to the point where this function was called, but to another point, chosen by whoever-crafted-this-packet. And this “another point” can easily be within the packet itself, allowing attacker to execute the arbitrary code. From this point, it is not your computer anymore.

This problem got so large proportions, that special NX bit was introduced to x86/x64 CPUs (at hardware level)1 to disallow code execution from those-pages-not-intended-for-this-purpose (this allows OS to mark stack pages with NX bit, preventing execution of arbitrary code with this type of attack). Still, while NX bit does protect from arbitrary code execution, it doesn’t provide 100% protection from all the buffer overflow-based attacks. Even with NX bit, the buffer overwrites can still cause crashes (i.e. allow to perform Denial-of-Service attack without the need to get millions of computers attacking) and can change program behaviour in unexpected ways.

With NX bit or not, having potential buffer overflows is a Really Bad Thing. So, NEVER EVER write code like the above. Instead, write your own wrapper around strcpy:

char* safe_strcpy(char* dst, size_t dst_sz, const char* src) {
  assert(dst&&src);//dst and src MUST NOT be NULL
  for(char* p=dst;dst_sz>0;--dst_sz) {
    char c = *src++;
    *p++ = c;
    if(c==0) return dst;
  }

  throw MyBufferOverflowException();
}

As soon as you have this safe_strcpy(), you can replace your existing strcpy() calls with the calls to safe_strcpy() without much changes (just being careful about passing the right size); in some cases I’ve even suggested defining a macro SAFE_STRCPY(buf,src) as something like

#define SAFE_STRCPY(buf,src) safe_strcpy(buf,sizeof(buf)/sizeof(buf[0]),src)

reducing potential for mistakes (mismatching buffer size and dst_sz) even further.

And of course, strcpy() is not the only function with a potential to cause this kind of problems, so you’ll need many safe_*() wrappers similar to the safe_strcpy() above.

Hare with omg face:The very call to strlen() for a potentially-non-null-terminated string can lead to “buffer overread”, causing a core dumpNow, there are several alternative approaches that I don’t recommend:

  • If you’re writing for Windows-only, you might use MSVC’s strcpy_s(), though I prefer safe_strcpy() above as (a) it already incorporates error handling (so caller code doesn’t need to care about this kind of things), and (b) as it works for any platform.
  • Another potential-but-not-recommended alternative would be to play with strncpy() directly within the code, but this way is too much error-prone.
  • Yet another potentially disastrous alternative is to “call strlen once and then use explicit-length memory operations” (as suggested, for example, in one of the answers here: [StackOverflowStrcpy_s]). First, it is still error-prone; second, and more important, is that the very call to strlen() for a potentially-non-null-terminated string can lead to “buffer overread” – i.e. reading beyond the memory-region-allowed-for-your-process, causing a dreaded core dump (exception 0xC0000005 on Windows).

And one alternative that is IMHO comparable to the safe_strcpy()/SAFE_STRCPY() above:

  • Using C++ std::string, one may avoid having on-stack buffers at all; however, you will still need wrappers to read_string_from_buffer() (this function still MUST have maximum string size as a parameter, both to avoid allocating too much, and to avoid problems with “buffer overread”, mentioned above).

Which to use – safe_strcpy()/SAFE_STRCPY() or std::string-based approach mentioned above – is up to you and depends on overall coding style in your project. However, there is one more important thing to remember about, it is related to clean separation of “already clean” and “still potentially dirty” data.

In the future, as soon as TR24731-1 is implemented (supposedly in C++14), there hopefully will be a better (and standard) alternative, but for now I don’t know of anything readily-available-and-cross-platform for this purpose 🙁 .


1 on some of non-86x CPUs it existed for a long while

 

57a. DO Shield most of your program from “Unsanitized” Data

Assertive hare:What is practically very important – is to keep all the “unsanitized” data in one place.The tricks described above will allow you to handle stuff-which-comes-from-the-network in a safe manner. Essentially, what we’re doing is converting data from potentially-dangerous form into a guaranteed-to-be-safe form. This process is known as “data sanitization”.

However, what is practically very important – is to keep all the “unsanitized” data in one place. Otherwise, the need to remember across all the code that “oh, this data is still dirty and needs to be handled with utmost care” quickly becomes a source of extremely-difficult-to-find security holes.

One simple approach which works well in practice is to keep all the dirty data as-close-to-recv()-call as possible, and to “sanitize” it (using methods described above) as soon as possible, passing only data-guaranteed-to-be-safe to the rest of the program.

58. DO provide your own Firewall for your Virtual Connections

If you’ve followed advice in item #47 in Part VI, you’ve implemented virtual connections over your TCP connections. As discussed back then, this approach has many advantages; however, it comes with a price in security department.

Hare pointing out:When you have several “virtual connections” within a single TCP connection, you've essentially created a tunnel which (as almost any other tunnel) may allow an attacker to perform certain attacks.When you have several “virtual connections” within a single TCP connection, you’ve essentially created a tunnel which (as almost any other tunnel) may allow an attacker to perform certain attacks. For example, your “virtual connection” may tell your “front-end server” to connect to a specific “back-end server”. That might allow an attacker to access “back-end server” interfaces (which might be not intended for the end-user, and might be not protected that much).

Worse than that – such attacks cannot be distinguished from the legitimate traffic by traditional firewalls: from the point of view of traditional IP/TCP firewall, there is not difference between the legitimate TCP-connection-from-front-end-server-to-back-end-server and the malicious TCP-connection-from-front-end-server-to-back-end-server (the latter established by the end-user request as a part of establishing a virtual connection). In other words – traditional firewalls won’t be able to monitor/restrict your “virtual connections”.

So, should you give up virtual connections? Not really – the only thing you need to do is to write your own firewall which will let you define what is allowed for virtual connections, and what is not.

It is certainly not so complicated as it sounds. Basically, all you need is to disable all “tunneling” of virtual connections by default (with security, “reject everything” unless-explicitly-told-otherwise MUST be a default), and then have a “rule table” which allows to specify what-kind-of-tunnel-is-allowed. An example of such a rule for a front-end server could be something like

ALLOW FROM * TO 10.2.3.0/24:1234 WHERE SERVICE='GameWorld'

Subnetwork For example, 192.168.1.0/24 is the prefix of the Internet Protocol Version 4 network starting at the given address, having 24 bits allocated for the network prefix, and the remaining 8 bits reserved for host addressing.— Wikipedia —, specifying that any end-user may request a tunnel to any IP address in 10.2.3.0/24 network on TCP port 1234, provided that the requested service on that port has name ‘GameWorld’.

Despite this complication (and complaints you’re bound to hear from the network admins who will need to configure these rules), for large-scale games virtual connections have been observed as well-worth it in the long run.

58a. DON’T use Real Back-End Server IPs to establish Virtual Connections

One issue, which is closely related to firewalls-for-your-virtual-connections, is using real IP addresses of the back-end servers when requesting a virtual connection. Client SHOULDN’T say “I want to connect to a server 10.2.3.4”; instead it SHOULD say “I want to connect to a server GameWorldServer23”, and the translation from GameWorldServer23 into the address 10.2.3.4 SHOULD be done by the front-end server. Violating this rule is not too fatal, but it exposes potentially sensitive information (about your internal network structure); for the same reason, using internal DNS for this mapping is also not exactly desirable (though again usually not too fatal).

This mapping from names to IPs SHOULD be configurable from the place which is somewhat close (in logical terms) to firewall tables, described above. Moreover, you MAY want to use names instead of IP addresses and ports, so the rule from the example in the previous item MAY become something like

ALLOW FROM * TO GameWorldServer* WHERE SERVICE='GameWorld'

provided that “somewhere close” there will be configured that

SERVER GameWorldServer12 10.2.3.2
SERVER GameWorldServer14 10.2.1.4
SERVER GameWorldServer23 10.2.9.9
SERVICE GameWorld PORT 1234

Which approach is better for your firewall rules (IP-based one or name-based one) – it depends (generally, name-based one is more flexible and a bit less error-prone security-wise, but your admins are likely to choose “10.1.2.3.4/24”; and as it is them who will need to configure this stuff – their voice needs to be taken into account).

59. DO help Users to Protect their Passwords

While for gaming purposes channel encryption is not really attacked too often (I don’t know of any such incidents, except for self-attacks for botting purposes, and it tells quite a lot), attacks on end-user computers are extremely common. There are all kinds of attacks – phishing, keyloggers, etc. etc. While protecting end-users credentials when-user-PC-is-compromised requires end-user hardware (providing so-called two-factor authentication), and is generally a complex topic, deserving a separate article – it is usually not that important to implement such complicated stuff from the very beginning; for now, let’s just make sure that you’re aware of the problem and are ready to dedicate resources to fight it as soon as games-on-top-of-your-engine reach, say, a few million users.

Surprised hare:Even if you fight very strongly against saving passwords on the basis of security, you will probably be forced to implement due to “overriding business considerations”One of the first problems you will face when dealing with user passwords, will be a requirement to save it to make life of the player simpler. Even if you fight very strongly against saving passwords on the basis of security, you will probably be forced to implement due to “overriding business considerations” (and this is one case when I will certainly agree with these considerations).

Most security-conscious people will agree that saving password as a plain-text (whether in file, Windows Registry, or wherever else) is a Really Bad Idea. However, many people will say “hey, what’s the problem? I will encrypt it with a key embedded into my app!” – and this is only marginally better than saving password as a plain text. Whatever key you embed into your app – it is easily accessible to the attacker, so you can assume that this nice-looking-sorta-encrypted password is not really encrypted, but merely scrambled, and can be retrieved with not-so-much effort.

59a. DO Consider Protected Storage for Storing User Passwords

If your target OS provides “protected storage” service, you should use it for saving passwords of your users. For example, on Windows, protected storage is normally accessed via CryptProtectData() function. The trick here is that with CryptProtectData(), you don’t need to care about the key to encrypt with; of course, the key does exist “beyond the scene”, but it is out of scope, and you don’t need to care about it.

One problem with this approach is that if attacker got a backdoor installed – he will be able to call CryptProtectData() himself and get all he needs (this includes passwords). To address it further, you may want to use the next trick (which is admittedly unconventional and security-by-obscurity, but has been observed working quite well in practice – i.e. being “better than nothing”).

59b. DO Consider Encrypting-Saved-User-Password with Something-Specific-To-User-Computer

Hare with an idea:The trick is that, when saving password, to encrypt it with 'something unique for the user account and/or computer where your app is running at the moment.So, can we do something if the attacker has the same rights as the victim? Well, not much, but we can throw in a healthy dose of obscurity, which doesn’t protect from real hackers, but may help against wannabe-script-kiddies (and you will be surprised how many of them are out there, especially when trying to break a game).

The trick is that, when saving password, to encrypt it with something-unique-for-the-user-account-and-or-computer-where-your-app-is-running-at-the-moment. This provides cheap and simple (though not really bulletproof) protection against stealing saved passwords.

The problem with this approach is that it is essentially security-by-obscurity, so I cannot give any specific advice what exactly is this “something-unique” you should use (as soon as I publish it, the attackers will write tools to gather this information from the computer, and protection will become useless). Still, despite this approach being a “security-by-obscurity” (and therefore is a fallacy from a traditional cryptographer’s point of view), you’ll be surprised how many of wannabe attackers this simple trick will stop.

This trick can be used either separately or simultaneously with protected storage (item #59a).

60. DO Hash Passwords when Storing on the Server Side

Hare asking question:What will happen if attacker got the whole database of your users' passwords?Hashing passwords before storing them on the server side, is a very popular topic, especially in wannabe-security circles. While IMNSHO its importance is greatly overrated (up to the atrociously wrong point that people start to get an impression that as-soon-as-passwords-in-DB-are-hashed, you don’t need to care about security), you still should do it (alongside with a few dozens of other things, which shouldn’t give the attacker access to your passwords in the first place).

The problem which password-hashing aims to solve, is “what will happen if attacker got the whole database of your users’ passwords?” If you just store passwords in your DB as a plain text – the attacker who-got-access-to-your-DB will be able to impersonate any of your users (well, as attacker has got access to your DB, he might have been able to do a lot of other very nasty things, but these are out of scope for this item). 2

One obvious measure to prevent this kind of password stealing would be to use some kind of crypto-hash (such as SHA-256 or something), and store hash instead of the password. Then, when user comes in with the password – you can calculate the same hash, and compare hashes.

A bit less obvious but necessary for security purposes (in particular, to have the same password look differently in DB) is to have a so-called “salt” (which is random for each password, and is stored in plain alongside with password hash), and to have

stored-hash = CRYPTO-HASH(salt||password)

, where || denotes concatenation. Having large (say,128+ bit) “salt” is also instrumental in preventing so-called “rainbow table attacks”.

Brute-force Attack A brute-force attack, or exhaustive key search, is a cryptanalytic attack that can, in theory, be used against any encrypted data— Wikipedia —However, even if you’re doing both things above – you’re not really safe. Due to specifics of passwords (which are short), a modern-day attacker can mount a brute-force attack, simply trying all the 1-letter passwords, then all the 2-letter ones, and so on. This brute-force attack works long as CRYPTO-HASH() function is fast. As described in [CodingHorror], these days, if a fast hash function is used, even truly random 9-10-letter passwords can be brute-forced in the matter of hours, and you’re not really safe with even truly random passwords until your user’s password is at least 12-letters long. So, to prevent this kind of attack,

Hash function which you’re using to hash your passwords, SHOULD BE REALLY SLOW

(more strictly, it should take lots of CPU resources, and preferably lots of RAM too).

This “hash function must be slow” consideration applies only to hashing passwords, and doesn’t apply to any other uses of hash functions. So, while SHA256 is (relatively) fast, it is vulnerable only for storing passwords, and is perfectly fine for any other uses.

scrypt is a password-based key derivation function... specifically designed to make it costly to perform large-scale custom hardware attacks by requiring large amounts of memory— Wikipedia —As a result of this (rather peculiar, I must say) requirement, a whole bunch of intentionally-slow-hash-functions has arised, including PBKDF2 and bcrypt (those these two functions are currently out of fashion), and more modern scrypt. When using any password hashing function, it is important to specify parameters (such as number of rounds for PBKDF2, ‘cost’ for bcrypt, and parameters N/r/p for scrypt) which ensure the-worst-possible-performance, which your game servers are still able to cope with. For further details, see [NCCGroup].

Bottom line:

DO hash your passwords using scrypt, with performance parameters which your servers can barely handle.

In addition, it is important to note that:

  • While it is important to protect your database in the first place, hashing passwords costs very little (see also below about hybrid-client-and-server-side password hashing), so there is no reason to do it. If nothing else, password hashing will improve perceptional security (or more strictly, if you don’t do it – you will be ostracized even if your security is so good that you won’t leak your password DB ever, and so the whole password-hashing issue will become moot).
  • While it is important to hash passwords, it certainly doesn’t mean that this is the only thing you need to do security-wise.
    The whole misperception of “We’re hashing passwords, so we’re fine security-wise” is much worse than not hashing passwords at all.

2 Of course, the whole problem makes sense only if you don’t pass your password over the network in open (you may still pass it over TLS-encrypted channel)

 

60a. DO Consider hybrid-client-and-server-side Password Hashing

This is one thing which I have never tried myself, but it looks solid at least on the first glance 🙂 . If you find a significant problem with the logic below – please let me know.

Surprised hare:If your server is handling 50000 users, and all of them reconnect at the same time (which does happen), you may need to calculate 50000 of those intentionally-slow-hashes within a few seconds.The main restraining factor for using even-slower hash functions is server limitations. In general, if your server is handling 50000 users (a very practical number for a 2-socket front-end server), and all of them reconnect at the same time (which does happen), you may need to calculate 50000 of those intentionally-slow-hashes within a few seconds. It either rules out hash functions that need over approx. 1ms to calculate (50’000*1ms/16 cores~=3 sec, and that is usually about as much as you can spare for this purpose) or needs much more hardware on the server side, which is otherwise necessary. For a 8-letter truly random password, attacking a hash-which-takes-1ms, would mean about 2.8e9 core-seconds; while the number sounds quite large, a million-box botnet can do it in about 15 minutes per password (we’re assuming that properly random and properly sized “salt” is in place, and that “rainbow table” speedups won’t help for multiple passwords; also we didn’t take attackers using GPUs, which might help them significantly, but hopefully not much if you’re using scrypt).

Of course, it all depends on the costs of the information you’re protecting, but what if your game is popular enough and has those artifacts worth thousands-of-dollars? Is there anything you can do to improve the situation without spending a lot on upgrading hardware?

Let’s consider the following schema.

The user has password P. Let client compute P’=CRYPTO-HASH1(P), which has, say, 256-bit length. Then, let’s pass this P’ over the network, and hash it again on the server side before storing: Pstore = CRYPTO-HASH2(P’).

This way, the brute-force attacker will need either to attack P’, or to attack directly P. However, brute force attack on P’ won’t really work as P’ is too long (and 2^256 password space is way too much to break); direct brute-force attack on P is still possible (well, we didn’t apply any kind of magic), but then attacker will face combined cost of CRYPTO-HASH1 and CRYPTO-HASH2. And the cost of CRYPTO-HASH1 can be made much higher than the cost of CRYPTO-HASH2 (of the order of 0.1-0.3s time to be barely noticeable by the end-user), making the attack cost for the attacker about 100x more expensive.

While this is certainly not a silver bullet, costs of implementing such “hybrid” client-and-server password hashing are minimal, and it can’t hurt (well, saving for implementation bugs).

61. DO Consider Signing your app even for PCs

When developing your app for most of modern app platforms (notably mobile and consoles), you will be forced to sign your app, otherwise it simply won’t run 3

However, there is one platform which has no mandatory code signing – it is PC. On PCs, while code signing is not mandatory, it is still a Good Idea to sign the code.

Hare thumb up:For Windows, code signing is known as Microsoft Authenticode, and the main benefit of doing it is that whenever user downloads signed executable, browsers tend to complain much less about your executable being “dangerous”For Windows, code signing is known as Microsoft Authenticode, and the main benefit of doing it is that whenever user downloads signed executable, browsers tend to complain much less about your executable being “dangerous”. This has been observed for IE for sure, and has been reported [StackOverflowChromeWarning] to help also for Chrome, though these things tend to change quickly. Still, it doesn’t hurt to have your code signed (well, except for the price of the Authenticode certificate, which is around a few hundred dollars per year), and it might help a little bit to avoid your potential users being scared, especially at early stages of your game lifecycle.

Oh, and while we’re at it – code signing can also help your users (at least those who have some clue) to distinguish your genuine software from unauthorized-software-which-is-bundled-with-god-knows-what, such as SourceForge-produced executables-with-stuffed-adware (see [ArsTechnica] for more details on SourceForge actions in this regard). While SourceForge seems to produce only relatively mildly annoying adware, you can count on hackers taking a copy of your game and conveniently bundling it with a keylogger (under control of the hacker, of course), so all the passwords your player enters, will be immediately known to the hacker. While the code signing does not protect user from him being careless himself, at least if your software is signed, you can try to explain your users that “if it doesn’t say ‘Signed by OUR-GREAT-COMPANY’ – it is fake”; it will work for about 10% of your users, but you will be able to point it out to the remaining 90%, which will help a little bit to deal with their complaints.


3 unless phone-or-console-you’re-running-your-app-on is jail-broken

 

61a. DO Sign your App Auto-Updates

About the same way that it happens with signing – if your game is mobile-only (or console-only) – then most likely updates of your app will be handled for you. However, if you’re on PC (notably on Windows), once again you’re on your own.

There are lots of auto-update programs out there, but only few of them are doing things right from the security perspective. To get a solid auto-update (the one which cannot be fooled by the attacker into installing whatever-attacker-wants instead of your real program), you MUST do the following:

  • Have an update app (well, probably you already have it regardless of security)
  • Have your own “root certificate” (a.k.a. “CA certificate”; those daring enough can store “root public key” instead, it won’t make any difference) embedded into your update app
  • Have each of your updates signed with “root private key” (a.k.a. “CA private key”)
  • When your update app gets an update – check that signature of the update is correct (check should be done against “root certificate”)

Surprisingly, very few update apps do this, but those who don’t do it – leave their users vulnerable to an attack where your own update app can be tricked into install a maliciously-modified app instead of the real one. The model described above, if implemented properly, provides an extremely solid protection against such an attack vector.

62. DON’T use Dangerous/outdated algorithms, even for Not-Security-Related Purposes

Hare wondering if you are crazy:It is better not to use such algorithms/practices even if your use of them has nothing to do with security. While not exactly in the realm of security, one consideration which comes from experience, is the following: there are certain algorithms and practices out there which are considered unsafe from security perspective. It is better not to use such algorithms/practices even if your use of them has nothing to do with security. It is just not worth the trouble of explaining everybody-and-his-dog that your use is fine.

To be more specific: once upon a time, we’ve been through a security audit. One of the findings of the audit was that we were using functions srand() and rand() to generate random numbers (which are considered very much unsafe in the security world). While our use of these (indeed very much insecure) functions has had nothing to do with security (and the auditors did note that we did use secure generators too, and that they weren’t sure if our use of these has implied  any security problems) – it still took us some time to double-check that the only use of this stuff has nothing to do with security, to write a document explaining it, to get confirmation that this kind of use is fine, etc. etc.

One more example: if you write a data transfer protocol, and use MD5 just as a kind of “improved CRC” (without any implications based on it’s security, just to protect from accidental changes on the line), and you publish this protocol – you can be sure that there will be quite a few people bashing you for using MD5 (and if you use CRC16 instead, which provides much worse guarantees against accidental modifications than MD5 – everybody will be fine with it).

Bottom line:

perceptional security does matter, and using algorithms-widely-considered-insecure is likely to hurt you, even if the real security is not affected at all

63. DO Consider adding security to an Existing Game Engine

If your favorite game engine doesn’t support security out-of-the-box – don’t worry too much, chances are that it is possible to add security on top of it.

For example, if speaking about Unity 3D, you should be able to secure each of its communication methods. For reliable UDP (“RPC calls” in Unity3D-speak), it should be possible to use TLS on top of it (though it may require some tinkering depending on your game’s specifics). For unreliable UDP (known as “unreliable synchronization” in Unity3D world) – you might use DTLS, though using OpenSSL’s implementation of DTLS is going to be quite tricky.

In any case – for any decent communication system it is possible to build a secure layer on top of it, and Unity 3D is not an exception.

64. DO Think About Attacks on Front-End Servers

IDS An intrusion detection system (IDS) is a device or software application that monitors network or system activities for malicious activities or policy violations and produces reports to a management station.— Wikipedia —For high-security games such as stock markets, you do need to think about possibility of your front-end servers being attacked. In such cases, a reasonably good security system will:

  • have an additional firewall between front-end servers and back-end servers (with IDS for a segment covering front-end servers). While this is not really a software development thing, you still need to know that such a firewall may be needed, and to support configurations with such a firewall present
  • protect data which traverses through your front-end servers from the attacks:
    • for point-to-point communications, consider adding another layer of protected end-to-end channel (from the client to the back-end server). This protected channel SHOULD run under your TLS encryption (and MAY be another completely isolated TLS itself – or it MAY be something different, even home-grown, though the latter is debatable). This way, front-end server will decrypt “outer” TLS, will understand where to route this end-to-end protected channel, but won’t be able to get within this end-to-end protected channel, even if the front-end server is under control of the attacker
    • for publisher/subscriber model  (see item #16 in part IIb) – consider signing the data being published (at the publisher), with a signature check at the subscriber. This will ensure that even if the front-end server is compromised – the attacker won’t be able to modify the publisher’s data.

While this kind of protection is not really mandatory for the most games out there – it is a Really Good Thing for highly sensitive games such as stock markets. Most of the attacks on your system will be directed at front-end servers, and having your critical data protected while your first line of defence is broken, your IDS is reporting you the attack, and your response team is busy dealing with the attack – can easily make a difference between being praised and being fired.

XX. DO Take All the Advice With a Good Pinch of Salt

There was quite a bit of advice given in our article on DO’s and DON’Ts of Network Programming for Games.

If you’re not a thinking kind of person – you can follow the advice in this article blindly. However, you can generally achieve much better results, if you question DO’s and DON’Ts (whether these ones or any others), try to understand the rationale behind, and see if it applies to your specific app (game/whatever). This is not a blanket permission to ignore whatever-you-don’t-like; you really need to understand the advice before ignoring it.

Good Luck, you Will Need It

Judging hare:Good Luck, you Will Need ItPhew, it was quite a job to write down all the items above. I’m not sure if the list is exhaustive, but I’m sure that if all the game developers would follow these DO’s and DON’Ts – life of the end-user would be significantly easier, bottom lines of the game development companies would be significantly more in the black, and overall the gamer world would be a much better place 😉

On a more serious note – while network support is not a thing which can make a game, it is a thing which can easily break an otherwise perfect multi-player game. Been there, seen that.

Don't like this post? Comment↯ below. You do?! Please share: ...on LinkedIn...on Reddit...on Twitter...on Facebook

Acknowledgement

Cartoons by Sergey GordeevIRL from Gordeev Animation Graphics, Prague.

Join our mailing list:

Comments

  1. says

    I am not a game developer, or even a low level programmer. I do simple PHP development. Still, I read your 64 network dos and don’ts all the way through and enjoyed the insight into the network side of this industry I don’t get to see or understand very much. I’d like to one day pick up more involved game development. I’m sure this will be a great reference. Thanks Bugs.

    • "No Bugs" Hare says

      Thanks! It always comes as a Big Incentive when I know that somebody finds it interesting 🙂

  2. Paul says

    Hi! I’m developing a game engine. When i read all of your brilliant tasty articles, i found that i’m following most of your advices and even can improve some! That’s kind of a really cool thing!
    Thanks for your time! Really appreciate this!

  3. Krzysztof says

    Hello! I’ve been reading Hare’s articles for a while and find them fascinating and really approachable – thank you for writing. About safe_strcpy: seeing throw keyword I assume we’re writing full-fledged C++, so we might as well use a bit of template trickery to get size of array:

    template
    char* safe_strcpy(char (&dst)[NDst], const char* src) {
    assert(dst && src); // dst and src MUST NOT be NULL
    ssize_t dst_sz = NDst;
    for(char* p = dst; dst_sz > 0; –dst_sz) {
    char c = *src++;
    *p++ = c;
    if (c == 0) return dst;
    }

    throw MyBufferOverflowException();
    }

    Performance-wise it should be the same. Code bloat / compilation dependencies may be limited by moving loop to inner safe_strcpy_impl. This way user may call as follows, without polluting namespace with macros. Moreover, function overloading may be used and more compile-time checks may be done.

    char packet[256];
    char buf[16];
    safe_strcpy(buf, packet);

    • "No Bugs" Hare says

      Yes, something along these lines is possible (BTW, sorry for WordPress eating angle brackets and everything within). Probably, it would be even better to have similar templatized constructor of gsl::span (from new Guidelines Support Library by Stroustrup and Sutter) out of char array, and then to have safe_strcpy to accept gsl::span (it would make the whole thing more generic).

  4. Ian Smith says

    “Well, not much, but we can throw in a healthy doze of obscurity,” Should be “dose” not “doze.”
    Having read your whole list, I was aware of 80% of it but the last 20% was invaluable. I’m surprised you didn’t cover the SELECT() cost for sockets – it is limited by file system handlers. poll() may be the better choice here for the same use case.

    A topic that may be good to weigh in on from an MMORPG engine + security standard is chat API’s and stripping HTML. Too often the chat API contains HTML parsing in order to provide linking of objects and map coordinate handlers, and then this has to be stripped out. I’d say “chat matters” in MMO these days.

    • "No Bugs" Hare says

      “doze” – fixed, thanks!

      About poll – it is a rather complicated subject (and not THAT obvious too) – see, for example, discussion here: http://ithare.com/network-programming-socket-peculiarities-threads-and-testing/ . In short – from what I’ve seen, select() works pretty well as long as there are not many idles, AND number of sockets is within a few hundreds; and as long as select() works pretty well – I usually prefer cross-platform stuff to platform-specific one (and if we’re going poll(), we may want to go epoll(), and so on). So for me it is more like “select() until we’re running into trouble (and it will never happen on the client) – or deeply platform-specific stuff such as epoll() or kqueue()”

      On chat and stripping HTML – yes, it is indeed a wide-spread problem, I’ll think where it may fit (probably Chapter XVI), THANKS!

Leave a Reply

Your email address will not be published. Required fields are marked *