{"id":2277,"date":"2024-10-04T15:54:23","date_gmt":"2024-10-04T13:54:23","guid":{"rendered":"https:\/\/blogs.gentoo.org\/mgorny\/?p=2277"},"modified":"2024-10-08T15:45:53","modified_gmt":"2024-10-08T13:45:53","slug":"testing-the-safe-time64-transition-path","status":"publish","type":"post","link":"https:\/\/blogs.gentoo.org\/mgorny\/2024\/10\/04\/testing-the-safe-time64-transition-path\/","title":{"rendered":"Testing the safe time64 transition path"},"content":{"rendered":"<p>Recently I&#8217;ve been elaborating on <a rel=\"external\" href=\"https:\/\/blogs.gentoo.org\/mgorny\/2024\/09\/28\/the-perils-of-transition-to-64-bit-time_t\/\">the perils of transition to 64-bit time_t<\/a>, following the debate within Gentoo.  Within these deliberations, I have also envisioned potential solutions to ensure that production systems could be migrated safely.<\/p>\n<p>My initial ideas involved treating time64 as a completely new ABI, with a new libdir and forced incompatibility between binaries.  This ambitious plan faced two disadvantages.  Firstly, it required major modification to various toolchains, and secondly, it raised compatibility concerns between Gentoo (and other distributions that followed this plan) and distributions that switched before or were going to switch without making similar changes.  Effectively, it would not only require a lot of effort from us, but also a lot of convincing other people, many of whom probably don&#8217;t want to spend any more time on doing extra work for 32-bit architectures.  This made me consider alternative ideas.<\/p>\n<p>One of them was to limit the changes to the transition period \u2014 use a <kbd>libt32<\/kbd> temporary library directory to prevent existing programs from breaking while rebuilds were performed, and then simply remove them, and be left with plain <kbd>lib<\/kbd> like other distributions that switched already.  In this post, I&#8217;d like to elaborate how I went about testing the feasibility of this solution.  Please note that this is not a migration guide \u2014 it includes steps that are meant to detect problems with the approach, and are not suitable for production systems.<br \/>\n<!--more--><\/p>\n<h2>Preparing to catch time32\/time64 mixing<\/h2>\n<p>As I&#8217;ve explained before, the biggest risk during the transition is accidental mixing of time32 and time64 binaries.  In the worst case, it could mean not only breaking programs running on production, but actively creating vulnerabilities via out-of-bounds accesses.  Therefore, I believe it is crucial to ensure that no such thing happens throughout the migration.<\/p>\n<p>My first step towards testing the migration process was to create an ABI mixing check that would be injected into executables.  I&#8217;ve placed the following code into <kbd>\/usr\/include\/__gentoo_time.h<\/kbd>:<\/p>\n<pre>#include &lt;stdio.h&gt;\r\n#include &lt;stdlib.h&gt;\r\n\r\n__attribute__((weak))\r\n__attribute__((visibility(\"default\")))\r\nstruct {\r\n\tint time32;\r\n\tint time64;\r\n} __gentoo_time_bits;\r\n\r\n__attribute__((constructor))\r\nstatic void __gentoo_time_check() {\r\n#if _TIME_BITS == 64\r\n#error \"not now\"\r\n\t__gentoo_time_bits.time64 = 1;\r\n#else\r\n\t__gentoo_time_bits.time32 = 1;\r\n#endif\r\n\r\n\tif (__gentoo_time_bits.time32 &amp;&amp; __gentoo_time_bits.time64) {\r\n\t\tFILE *f;\r\n\t\tfprintf(stderr, \"time32 and time64 ABI mixing detected\\n\");\r\n\t\t\/* trigger a sandbox failure for good measure too *\/\r\n\t\tf = fopen(\"\/time32-time64-mixing\", \"w\");\r\n\t\tif (f)\r\n\t\t\tfclose(f);\r\n\t\tabort();\r\n\t}\r\n}<\/pre>\n<p>Then, I have added the following line to <kbd>\/usr\/include\/time.h<\/kbd>, just above <kbd>__BEGIN_DECLS<\/kbd>:<\/p>\n<pre>#include &lt;__gentoo_time.h&gt;<\/pre>\n<p>Now, this meant that any binary including <kbd>&lt;time.h&gt;<\/kbd>, even indirectly, would get our check.  In fact, the check would probably be duplicated a lot, but that&#8217;s not really a problem for the test system.<\/p>\n<p>The check itself utilizes a bit of magic.  It creates a weak <kbd>__gentoo_time_bits<\/kbd> structure that would be shared between the executable itself and all loaded libraries.  Every binary would run the constructor function upon loading, and it would fits store its own <kbd>_TIME_BITS<\/kbd> value within the shared structure, and then ensure that no binary set the other value.  If that did happen, it would not only cause the program to immediately abort, but also try to trigger a sandbox failure, so the package build would be considered failed even if the build system ignored that particular failure.<\/p>\n<p>However, note the <kbd>#error<\/kbd> in the snippet.  This is a temporary hack to block packages that automatically try to use <kbd>-D_TIME_BITS=64<\/kbd> (e.g. coreutils, grep, man-db), as they would trigger the check prematurely, and as a false positive.<\/p>\n<p>At this point, I did rebuild the whole system, except for glibc, to inject the check into as many time32 binaries as possible:<\/p>\n<pre>emerge -ve --exclude=sys-libs\/glibc --keep-going=y --jobs=16 @world<\/pre>\n<p>A number of packages fail here, because they attempt to force <kbd>-D_TIME_BITS=64<\/kbd>.  This is okay, we don&#8217;t need perfect coverage, and we definitely don&#8217;t want false positives.<\/p>\n<h2>Preparing for the transition<\/h2>\n<p>The next step is to actually prepare for the transition.  The preparation involves two changes, to all packages except for <kbd>sys-libs\/glibc<\/kbd>:<\/p>\n<ol>\n<li>Moving all libraries from <kbd>lib<\/kbd> to <kbd>libt32<\/kbd>.<\/li>\n<li>Injecting <kbd>libt32<\/kbd> directories into <kbd>RUNTIME<\/kbd> of all binaries, executables and libraries alike.<\/li>\n<\/ol>\n<p>This is done using a tool called <a rel=\"external\" href=\"https:\/\/github.com\/projg2\/time32-prep\/\">time32-prep<\/a>.  It takes care of finding all potential libdirs from ld.so, setting <kbd>RUNPATH<\/kbd> on binaries (and removing any references to plain <kbd>lib<\/kbd>, while at it), and then moving the libraries.<\/p>\n<h2>Rebuilding everything<\/h2>\n<p>The next step is to configure the system to compile time64 binaries by default.  For a start, I have added the following snippet to <kbd>make.conf<\/kbd>, to easily distinguish packages that were rebuilt:<\/p>\n<pre>CHOST=\"i686-pc-linux-gnut64\"\r\nCHOST_x86=\"i686-pc-linux-gnut64\"<\/pre>\n<p>I&#8217;ve rebuilt the dependencies of GCC using time64 flags explicitly:<\/p>\n<pre>CFLAGS=\"-D_FILE_OFFSET_BITS=64 -D_TIME_BITS=64\" emerge -1v sys-apps\/sandbox dev-libs\/{gmp,mpfr,mpc} sys-libs\/zlib app-arch\/{xz-utils,zstd}<\/pre>\n<p>Rebuilt and switched binutils:<\/p>\n<pre>emerge -1v sys-devel\/binutils\r\nbinutils-config 1<\/pre>\n<p>Then, I&#8217;ve added a user patch to make GCC default to time64:<\/p>\n<pre>--- a\/gcc\/c-family\/c-cppbuiltin.cc\r\n+++ b\/gcc\/c-family\/c-cppbuiltin.cc\r\n@@ -1560,6 +1560,9 @@ c_cpp_builtins (cpp_reader *pfile)\r\n     builtin_define_with_int_value (\"_FORTIFY_SOURCE\", GENTOO_FORTIFY_SOURCE_LEVEL);\r\n #endif\r\n \r\n+  cpp_define (pfile, \"_FILE_OFFSET_BITS=64\");\r\n+  cpp_define (pfile, \"_TIME_BITS=64\");\r\n+\r\n   \/* Misc.  *\/\r\n   if (flag_gnu89_inline)\r\n     cpp_define (pfile, \"__GNUC_GNU_INLINE__\");<\/pre>\n<p>And rebuilt GCC itself (without time64 flags):<\/p>\n<pre>USE=-sanitize emerge -v sys-devel\/gcc\r\ngcc-config 1<\/pre>\n<p>Note that I had to disable sanitizers, as they currently fail to build with <kbd>_TIME_BITS=64<\/kbd>.  I also had to comment out the <kbd>__gentoo_time.h<\/kbd> include for the time of building GCC.<\/p>\n<p>The final step was to rebuild all packages (except for GCC and glibc) with the new compiler:<\/p>\n<pre>emerge -ve --exclude=sys-libs\/glibc --exclude=sys-devel\/{binutils,gcc} --jobs=16 --keep-going=y @world<\/pre>\n<h2>The results<\/h2>\n<p>Well, I have some bad news \u2014 at some point, the rebuilds started failing.  However, it seems that all failures I&#8217;ve hit during the initial testing can be accounted for as something relatively harmless \u2014 Perl and Python extensions.<\/p>\n<p>Long story short, since they are installed into a dedicated directory, they can&#8217;t be prevented from ABI mixing via the <kbd>libt32<\/kbd> hack.  However, that&#8217;s unlikely to be a real problem.  They failed for me, because I&#8217;ve made ABI mixing absolutely fatal \u2014 but in reality only private parts of the Python API use <kbd>time_t<\/kbd>, and these should not be used by any third-party extensions.  And in the end, the issues are resolved by rebuilding in a different order.<\/p>\n<h2>Next steps<\/h2>\n<p>While this could be considered an important success, we&#8217;re still way ahead from being ready to go full time64.  The time32-prep tool itself has a few TODOs, and definitely needs testing on a more &#8220;production-like&#8221; system.  Then, there are actual problems that the packages are facing on time64 setups (like the GCC build failure in sanitizers), and that need to be fixed before we make things official.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Recently I&#8217;ve been elaborating on the perils of transition to 64-bit time_t, following the debate within Gentoo. Within these deliberations, I have also envisioned potential solutions to ensure that production systems could be migrated safely. My initial ideas involved treating time64 as a completely new ABI, with a new libdir and forced incompatibility between binaries. &hellip; <a href=\"https:\/\/blogs.gentoo.org\/mgorny\/2024\/10\/04\/testing-the-safe-time64-transition-path\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Testing the safe time64 transition path&#8221;<\/span><\/a><\/p>\n","protected":false},"author":137,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true},"categories":[3,8],"tags":[],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/blogs.gentoo.org\/mgorny\/wp-json\/wp\/v2\/posts\/2277"}],"collection":[{"href":"https:\/\/blogs.gentoo.org\/mgorny\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.gentoo.org\/mgorny\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.gentoo.org\/mgorny\/wp-json\/wp\/v2\/users\/137"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.gentoo.org\/mgorny\/wp-json\/wp\/v2\/comments?post=2277"}],"version-history":[{"count":32,"href":"https:\/\/blogs.gentoo.org\/mgorny\/wp-json\/wp\/v2\/posts\/2277\/revisions"}],"predecessor-version":[{"id":2310,"href":"https:\/\/blogs.gentoo.org\/mgorny\/wp-json\/wp\/v2\/posts\/2277\/revisions\/2310"}],"wp:attachment":[{"href":"https:\/\/blogs.gentoo.org\/mgorny\/wp-json\/wp\/v2\/media?parent=2277"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.gentoo.org\/mgorny\/wp-json\/wp\/v2\/categories?post=2277"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.gentoo.org\/mgorny\/wp-json\/wp\/v2\/tags?post=2277"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}