You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

166 lines
8.8KB

  1. The updater program's code is public domain. The rest of ioquake3 is not.
  2. The source code to the autoupdater is in the code/autoupdater directory.
  3. There is a small piece of code in ioquake3 itself at startup, too; this is
  4. in code/sys/sys_autoupdater.c ...
  5. (This is all Unix terminology, but similar approaches on Windows apply.)
  6. The updater is a separate program, written in C, with no dependencies on
  7. the game. It (statically) links to libcurl and uses the C runtime, but
  8. otherwise has no external dependencies. It has to be a single binary file
  9. with no shared libraries.
  10. The basic flow looks like this:
  11. - The game launches as usual.
  12. - Right after main() starts, the game creates a pipe, forks off a new process,
  13. and execs the updater in that process. The game won't ever touch the pipe
  14. again. It's just there to block the child app until the game terminates.
  15. - The updater has no UI. It writes a log file.
  16. - The updater downloads a manifest from a known URL over https://, using
  17. libCurl. The base URL is platform-specific (it might be
  18. https://example.com/mac/, or https://example.com/linux-x86/, whatever).
  19. The url might have other features, like a updater version or a specific
  20. product name, etc.
  21. The manifest is at $BASEURL/manifest.txt
  22. - The updater also downloads $BASEURL/manifest.txt.sig, which is a digital
  23. signature for the manifest. It checks the manifest against this signature
  24. and a known public RSA key; if the manifest doesn't match the signature,
  25. the updater refuses to continue.
  26. - The manifest looks like this: three lines per item...
  27. Contents/MacOS/baseq3/uix86_64.dylib
  28. 332428
  29. a49bbe77f8eb6c195265ea136f881f7830db58e4d8a883b27f59e1e23e396a20
  30. - That's the file's path, its size in bytes, and an sha256 hash of the data.
  31. - The file will be at this path under the base url on the webserver.
  32. - The manifest only lists files that ever needed updating; it's not necessary
  33. to list every file in the game's installation (unless you want to allow the
  34. entire game to download).
  35. - The updater will check each item in the manifest:
  36. - Does the file not exist in the install? Needs downloading.
  37. - Does the file have a different size? Needs downloading.
  38. - Does the file have a different sha256sum? Needs downloading.
  39. - Otherwise, file is up to date, leave it alone.
  40. - If an item needs downloading, do these same checks against the file in the
  41. download directory (if it's already there and matches, don't download again.)
  42. - Download necessary files with libcurl, put it in a download directory.
  43. - The downloaded file is also checked for size and sha256 vs the manifest, to
  44. make sure there was no corruption or confusion. If a downloaded file doesn't
  45. match what was expected, the updater aborts and will try again next time.
  46. This could fail checksum due to i/o errors and compromised security, but
  47. it might just be that a new version was being published and bad luck
  48. happened, and a retry later could correct everything.
  49. - If the updater itself needs upgrading, we deal with that first. It's
  50. downloaded, then the updater relaunches from the downloaded binary with
  51. a special command line. That relaunched process copies itself to the proper
  52. location, and then relaunches _again_ to restart the normal updating
  53. process with the new updater in its correct position.
  54. - Once the downloads are complete and the updater itself doesn't need
  55. upgrading, we are ready to start the normal upgrade. Since we can't replace
  56. executables on some platforms while they are running, and swapping out a
  57. game's data files at runtime isn't wise in general, the updater will now
  58. block until the game terminates. It does this by reading on the pipe that
  59. the game created when forking the updater; since the game never writes
  60. anything to this pipe, it causes the updater to block until the pipe closes.
  61. Since the game never deliberately closes the pipe either, it remains open
  62. until the OS forcibly closes it as the game process terminates. Being an
  63. unnamed pipe, it just vaporizes at this point, leaving no state that might
  64. accidentally hang us up later, like a global semaphore or whatnot. This
  65. technique also lets us localize the game's code changes to one small block
  66. of C code, with no need to manage these resources elsewhere.
  67. - As a sanity check, the updater will also kill(game_process_id, 0) until it
  68. fails, sleeping for 100 milliseconds between each attempt, in case the
  69. process is still being cleaned up by the OS after closing the pipe.
  70. - Once the updater is confident the game process is gone, it will start
  71. upgrading the appropriate files. It does this in two steps: it moves
  72. the old file to a "rollback" directory so it's out of the way but still
  73. available, then it moves the newly-downloaded file into place. Since these
  74. are all simple renames and not copies, this can move fast. Any missing
  75. parent directories are created, in case the update is adding a new file
  76. in a directory that didn't previously exist.
  77. - If something goes wrong at this point (file i/o error, etc), the updater
  78. will roll back the changes by deleting the updated files, and moving the
  79. files in the "rollback" directory back to their original locations. Then
  80. the updater aborts.
  81. - If nothing went wrong, the rollback files are deleted. And we are officially
  82. up to date! The updater terminates.
  83. The updater is designed to fail at any point. If a download fails, it'll
  84. pick up and try again next time, etc. Completed downloads will remain, so it
  85. will just need to download any missing/incomplete files.
  86. The server side just needs to be able to serve static files over HTTPS from
  87. any standard Apache/nginx/whatever process.
  88. Failure points:
  89. - If the updater fails when still downloading data, it just picks up on next
  90. restart.
  91. - If the updater fails when replacing files, it rolls back any changes it has
  92. made.
  93. - If the updater fails when rolling back, then running the updater again after
  94. fixing the specific problem (disk error, etc?) will redownload and replace
  95. any files that were left in an uncertain state. The only true point of
  96. risk is crashing during a rollback and then having the updater bricked for
  97. some reason, but that's an extremely small surface area, knock on wood.
  98. - If the updater crashes or totally bricks, ioquake3 should just keep being
  99. ioquake3. It will still launch and play, even if the updater is quietly
  100. segfaulting in the background on startup.
  101. - If an update bricks ioquake3 to the point where it can't run the updater,
  102. running the updater directly should let it recover (assuming a future update
  103. fixes the problem).
  104. - If the download server is compromised, they would need the private key
  105. (not stored on the download server) to alter the manifest to serve
  106. compromised files to players. If they try to change a file or the manifest,
  107. the updater will know to abort without updating anything.
  108. - If the private key is compromised, we generate a new one, ship new
  109. installers with an updated public key, and re-sign the manifest with the
  110. new private key. Existing installations will never update again until they
  111. do a fresh install, or at least update their copy of the public key.
  112. How manifest signing works:
  113. Some admin will generate a public/private key with the rsa_make_keys program,
  114. keeping the private key secret. Using the private key and the rsa_sign
  115. program, the admin will sign the manifest, generating manifest.txt.sig.
  116. The public key ships with the game (adding 270 bytes to the download), the
  117. .sig is downloaded with the manifest by the autoupdater (256 bytes extra
  118. download), then the autoupdater checks the manifest against the signature
  119. with the public key. if the signature isn't valid (the manifest was tampered
  120. with or corrupt), the autoupdater refuses to continue.
  121. If the manifest is to be trusted, it lists sha256 checksums for every file to
  122. download, so there's no need to sign every file; if they can't tamper with the
  123. manifest, they can't tamper with any other file to be updated since the file's
  124. listed sha256 won't match.
  125. If the private key is compromised, we generate new keys and ship new
  126. installers, so new installations will be able to update but existing ones
  127. will need to do a new install to keep getting updates. Don't let the private
  128. key get compromised. The private key doesn't go on a public server. Maybe it
  129. doesn't even live on the admin's laptop hard drive.
  130. If the download server is compromised and serving malware, the autoupdater
  131. will reject it outright if they haven't compromised the private key, generated
  132. a new manifest, and signed it with the private key.
  133. Items to consider for future revisions:
  134. - Maybe put a limit on the number manifest downloads, so we only check once
  135. every hour? Every day?
  136. - Channels? Stable (what everyone gets by default), Nightly (once a day),
  137. Experimental (some other work-in-progress branch), Bloody (literally the
  138. latest commit).
  139. - Let mods update, separate from the main game?
  140. Questions? Ask Ryan: icculus@icculus.org
  141. --ryan.