tedunangst_flak.rss.xml - sfeed_tests - sfeed tests and RSS and Atom files
(HTM) git clone git://git.codemadness.org/sfeed_tests
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) LICENSE
---
tedunangst_flak.rss.xml (28011B)
---
1 <?xml version="1.0" encoding="UTF-8"?>
2 <rss version="2.0">
3 <channel>
4 <title>flak</title>
5 <link>https://flak.tedunangst.com/</link>
6 <description>flak rss</description>
7 <managingEditor>tedu@tedunangst.com</managingEditor>
8 <image>
9 <url>https://flak.tedunangst.com/icon.png</url>
10 <title>flak rss</title>
11 <link>https://flak.tedunangst.com/</link>
12 </image>
13 <item>
14 <title>quick thoughts on bouncy castle bcrypt broken compare</title>
15 <description><![CDATA[A few thoughts on the BCBCBC vulnerability. <a href="https://www.synopsys.com/blogs/software-security/cve-2020-28052-bouncy-castle/">Original report</a>. There's a few things not explicitly stated in the report, which I thought may be interesting.<p><b>vuln</b><p>To recap, the bug is that password hashes are compared by looking at the position of each character value, instead of comparing the character values at each position. This leads to a great many false positives, effectively a password bypass.<p>Here's a few hashes to review. As a reminder, hashes are structured as algorithm identifier, log round count, then a base64 encoding of the salt followed by the encrypted password. (Password: password)<p><pre><code>$2b$08$EVUJdN.PNZbjUOi9D3nsJecEYZE2jN0dr1/3CEvawNH.d5lp9Nt9G
16 $2b$08$TMwmj0nJfvO6eXGRTNoeaOGbivW1wvSAklXatjMo7tRwoo5FCxCTu
17 $2b$08$p31golX/c.Lz3mh0drcoU.O5Z/6NwzfWR6v7W5RbXKZnOGveumEja</code></pre><p>The comparison function looks at the index for the first 60 characters. For many of these characters, they will never appear in a password hash. Consulting <a href="https://man.openbsd.org/ascii">the ascii table</a> we can see that the first 32 are control characters, definitely not in a hash (which is mostly base64 encoded). Then comes some punctuation and the numbers. The entire alphabet, upper and lower, come after 60, and so the comparison function never looks for them.<p>So we can immediately see that for most hashes, the majority of the hash will not be looked at. We can also see that the '$' character will always match, as will '2', '0' and '8'. (In these examples. Other examples with e.g. 12 log rounds will match '1' and '2'.) Even if a '0' appears later in the hash, as in the first two hashes above, they will compare equal because the first '0' is in the same position.<p>This leaves only a very limited set of characters to match on. I'll rewrite the first hash with 'X' for unmatched characters. And again, with repeated letters removed as well, since we only compare index of the first occurrence.<p><pre><code>$2b$08$EVUJdN.PNZbjUOi9D3nsJecEYZE2jN0dr1/3CEvawNH.d5lp9Nt9G
18 $2b$08$XXXXXX.XXXXXXXX9X3XXXXXXXXX2XX0XX1/3XXXXXXX.X5XX9XX9X
19 $2b$08$XXXXXX.XXXXXXXX9X3XXXXXXXXXXXXXXX1/XXXXXXXXXX5XXXXXXX</code></pre><p>I don't know what password generates those hashes, but they do exist. As we can see, there are only six significant characters to match. Not many. We could brute force guess our way to logging in quite easily.<p>Here's two more hashes with custom salts.<p><pre><code>$2b$08$./0123456789XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
20 $2b$08$XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX./0123456789</code></pre><p>Any and every possible password will auth against the first hash. No matter what password is entered by an attacker, the salt will "absorb" all the possible comparisons. So this is the worst possible case. Neither the attacker nor user has control over the salt, it's blind luck, but some users will be very unlucky. The second hash is the reverse. If the salt only contains uncompared characters, then it's possible they appear in the password half of the hash. The second user here got lucky, it will take at least a few more guesses to find another password that matches. This is the best, but also unlikely, case, and it's still not great.<p><b>impact</b><p>I don't know enough about the java ecosystem to know who uses bcrypt from bouncy castle. I kinda thought most people used <a href="https://www.mindrot.org/projects/jBCrypt/">jBCrypt</a>, but that's guesswork.<p><b>cause</b><p>The commit introducing this was a switch to constant time comparison functions. The irony is that this isn't needed for password hashes. There's no useful information leakage that comes from comparing a password hash letter by letter.<p>That said, people fuss about such comparisons, and it can be easier to simply use a constant time function instead of analyzing each case (and then endlessly repeating that justification to people who haven't done the analysis). So the change is reasonable, if unnecessary.<p>I think the lesson is to use a constant time comparison function that is known to work instead of coding from scratch.<p><b>discovery</b><p>I'm not sure, but I'd guess this bug was found by inspection. That seems the best way to find this bug.<p>It may be possible to create a test case for this bug, but I'm not surprised it doesn't exist. Most of the time, a different password will still correctly compare unequal. You'd need to test many, many passwords against a single hash, all to find a bug which you probably didn't consider could exist.]]></description>
21 <category>programming</category>
22 <category>security</category>
23 <link>https://flak.tedunangst.com/post/quick-thoughts-on-bouncy-castle-bcrypt-broken-compare</link>
24 <pubDate>Tue, 22 Dec 2020 17:53:56 UTC</pubDate>
25 <guid isPermaLink="true">https://flak.tedunangst.com/post/quick-thoughts-on-bouncy-castle-bcrypt-broken-compare</guid>
26 </item>
27 <item>
28 <title>small views of large files</title>
29 <description><![CDATA[Sometimes you have a large file when you want a small file. You may not be able to edit the large file, but that's okay, you can simply read the small part you want out of the large file. <b>libfdview</b> is a proof of concept library that presents a smaller view of a larger file.<br><h4>concept</h4><p>We will LD_PRELOAD a library that overrides <code>open</code> and associated functions. When the application reads from the large file, it will instead be limited to a smaller view of the file.<p>We grab the next <code>open</code> function so we don't tie ourselves up in loops, and set a few variables passed via environment.<p><pre><code><span class=tp>static</span> <span class=tp>const</span> <span class=tp>char</span> <span class=op>*</span>viewfile;
30 <span class=tp>static</span> off_t viewstart<span class=op>,</span> viewend;
31
32 <span class=tp>static</span> <span class=tp>void</span> init<span class=op>()</span> __attribute__ <span class=op>((</span>constructor<span class=op>))</span>;
33 <span class=tp>static</span> <span class=tp>void</span> init<span class=op>()</span>
34 <span class=op>{</span>
35 realopen <span class=op>=</span> dlsym<span class=op>(</span>RTLD_NEXT<span class=op>,</span> <span class=st>"open"</span><span class=op>)</span>;
36 viewfile <span class=op>=</span> getenv<span class=op>(</span><span class=st>"FDVIEW_NAME"</span><span class=op>)</span>?<span class=op>:</span><span class=st>""</span>;
37 viewstart <span class=op>=</span> strtonum<span class=op>(</span>getenv<span class=op>(</span><span class=st>"FDVIEW_START"</span><span class=op>)</span>?<span class=op>:</span><span class=st>"0"</span><span class=op>,</span> <span class=nm>0</span><span class=op>,</span> <span class=nm>10987654321</span><span class=op>,</span> <span class=bi>NULL</span><span class=op>)</span>;
38 viewend <span class=op>=</span> strtonum<span class=op>(</span>getenv<span class=op>(</span><span class=st>"FDVIEW_END"</span><span class=op>)</span>?<span class=op>:</span><span class=st>"10987654321"</span><span class=op>,</span> viewstart<span class=op>,</span> <span class=nm>10987654321</span><span class=op>,</span> <span class=bi>NULL</span><span class=op>)</span>;
39 <span class=op>}</span></code></pre><p>It's cleaner IMO to use a <i>constructor</i> <code>init</code> function, but lazy init would work here too.<p>Whenever a file is opened, we check if it's our target. If so, we seek forward past the part of the file that shouldn't be visible.<p><pre><code><span class=tp>static</span> <span class=tp>int</span> viewfd <span class=op>=</span> <span class=op>-</span><span class=nm>1</span>;
40
41 <span class=tp>int</span>
42 open<span class=op>(</span><span class=tp>const</span> <span class=tp>char</span> <span class=op>*</span>path<span class=op>,</span> <span class=tp>int</span> flags<span class=op>,</span> <span class=tp>int</span> mode<span class=op>)</span>
43 <span class=op>{</span>
44 <span class=tp>int</span> fd <span class=op>=</span> realopen<span class=op>(</span>path<span class=op>,</span> flags<span class=op>,</span> mode<span class=op>)</span>;
45 <span class=kw>if</span> <span class=op>(</span>strcmp<span class=op>(</span>path<span class=op>,</span> viewfile<span class=op>)</span> <span class=op>==</span> <span class=nm>0</span><span class=op>)</span> <span class=op>{</span>
46 viewfd <span class=op>=</span> fd;
47 lseek<span class=op>(</span>viewfd<span class=op>,</span> viewstart<span class=op>,</span> SEEK_SET<span class=op>)</span>;
48 <span class=op>}</span>
49 <span class=kw>return</span> fd;
50 <span class=op>}</span></code></pre><p>Var args are messy.<br><h4>fopen</h4><p>The first problem is that trying to perform a simple test with <b>head</b> doesn't work. Internally, head calls <code>fopen</code> which is wired up to use the libc version of open, not any preloaded versions. Another override.<p><pre><code>FILE <span class=op>*</span>
51 fopen<span class=op>(</span><span class=tp>const</span> <span class=tp>char</span> <span class=op>*</span>path<span class=op>,</span> <span class=tp>const</span> <span class=tp>char</span> <span class=op>*</span>mode<span class=op>)</span>
52 <span class=op>{</span>
53 <span class=kw>if</span> <span class=op>(</span>strcmp<span class=op>(</span>path<span class=op>,</span> viewfile<span class=op>)</span> <span class=op>==</span> <span class=nm>0</span><span class=op>)</span> <span class=op>{</span>
54 <span class=tp>int</span> fd <span class=op>=</span> open<span class=op>(</span>path<span class=op>,</span> <span class=nm>0</span><span class=op>,</span> <span class=nm>0</span><span class=op>)</span>;
55 <span class=kw>return</span> fdopen<span class=op>(</span>fd<span class=op>,</span> mode<span class=op>)</span>;
56 <span class=op>}</span>
57 <span class=kw>return</span> realfopen<span class=op>(</span>path<span class=op>,</span> mode<span class=op>)</span>;
58 <span class=op>}</span></code></pre><p>The mode handling is rather incomplete, but good enough for production.<br><h4>read</h4><p>If we don't want to read past the end of our small view, we'll need to fix <code>read</code> as well.<p><pre><code>ssize_t
59 read<span class=op>(</span><span class=tp>int</span> fd<span class=op>,</span> <span class=tp>void</span> <span class=op>*</span>buf<span class=op>,</span> size_t n<span class=op>)</span>
60 <span class=op>{</span>
61 <span class=kw>if</span> <span class=op>(</span>fd <span class=op>==</span> viewfd<span class=op>)</span> <span class=op>{</span>
62 off_t pos <span class=op>=</span> lseek<span class=op>(</span>fd<span class=op>,</span> <span class=nm>0</span><span class=op>,</span> SEEK_CUR<span class=op>)</span>;
63 <span class=kw>if</span> <span class=op>(</span>n <span class=op>+</span> pos <span class=op>></span> viewend<span class=op>)</span>
64 n <span class=op>=</span> viewend <span class=op>-</span> pos;
65 <span class=op>}</span>
66 <span class=kw>return</span> realread<span class=op>(</span>fd<span class=op>,</span> buf<span class=op>,</span> n<span class=op>)</span>;
67 <span class=op>}</span></code></pre><p>At some point we should probably take care to override <code>lseek</code> as well to make sure that the application doesn't itself seek out of the view.<br><h4>results</h4><p><pre><code>head -1 fdview.c
68 #include <dlfcn.h>
69 LD_PRELOAD=./libfv.so FDVIEW_NAME=fdview.c FDVIEW_START=19 head -1 fdview.c
70 #include <stdio.h></code></pre><p>Yes. And with a simple test program that's basically <code>cp</code> to stdout.<p><pre><code>LD_PRELOAD=./libfv.so FDVIEW_NAME=tester.c FDVIEW_END=39 ./tester
71 fd: 3
72 #include <stdio.h>
73 #include <unistd.h></code></pre><p>There's more to the file, but we don't see it because we've been cut off from reading more.<br><h4>long tail</h4><p>There's no test for <code>tail</code> because, uh, it starts getting more complicated, and my hard drive is big enough to store both large and small files, but it could be done given sufficient functions overrides.]]></description>
74 <category>c</category>
75 <category>programming</category>
76 <link>https://flak.tedunangst.com/post/small-views-of-large-files</link>
77 <pubDate>Tue, 22 Sep 2020 20:00:47 UTC</pubDate>
78 <guid isPermaLink="true">https://flak.tedunangst.com/post/small-views-of-large-files</guid>
79 </item>
80 <item>
81 <title>comparative unsafety</title>
82 <description><![CDATA[I wrote some <b>rust</b> code. I used <i>unsafe</i>. It was unsafe. After months of contemplating this unfortunate result, I've found someone else to blame.<p>For context, I was writing an smtp server. Not necessarily a good smtp server, but the kind of smtp server you'd write in two days to see what happens. As such, any conclusions from this experiment are invalid.<br><h4>gift</h4><p>At the heart of the problem, and core to an smtp server, is a function to gift files from the server to the recipient.<p><pre><code><span class=st>#[link(name = "c")]</span>
83 <span class=kw>extern</span> <span class=st>"C"</span> <span class=op>{</span>
84 <span class=kw>fn</span> chown<span class=op>(</span>path<span class=op>:</span> <span class=op>*</span><span class=tp>const</span> <span class=tp>i8</span><span class=op>,</span> uid<span class=op>:</span> <span class=tp>i32</span><span class=op>,</span> gid<span class=op>:</span> <span class=tp>i32</span><span class=op>)</span> <span class=op>-></span> <span class=tp>i32</span>;
85 <span class=op>}</span>
86
87 <span class=kw>fn</span> gift<span class=op>(</span>filename<span class=op>:</span> <span class=tp>&String</span><span class=op>,</span> userid<span class=op>:</span> <span class=tp>i32</span><span class=op>)</span> <span class=op>{</span>
88 <span class=bi>unsafe</span> <span class=op>{</span>
89 <span class=kw>let</span> path <span class=op>=</span> <span class=tp>CString::</span><span class=bi>new</span><span class=op>(</span>filename<span class=op>.</span><span class=bi>as_str</span><span class=op>()).</span><span class=bi>unwrap</span><span class=op>().</span>as_ptr<span class=op>()</span>;
90 <span class=kw>let</span> none <span class=op>=</span> <span class=op>-</span><span class=nm>1</span>;
91 chown<span class=op>(</span>path<span class=op>,</span> userid<span class=op>,</span> none<span class=op>)</span>;
92 <span class=op>}</span>
93 <span class=op>}</span></code></pre><p>Why am I using my own ffi version of <i>chown</i> instead of the libc crate? The libc crate prototypes <i>chown</i> with unsigned <i>uid_t</i> and <i>gid_t</i> types, and I want to pass -1 because I'm not interested in changing the group. I'll spare you the long rant about how one should never redeclare system interfaces if you can't take the time to do so properly, because it turns out if you dig into it, <i>uid_t</i> boils down to <i>uint32_t</i>, but even so, the <a href="http://man.openbsd.org/chown.2">manual for chown</a> says -1 should work and I want it to work. Using the libc chown and adding <i>try_into</i> to <i>none</i> causes it to panic.<p>In the face of adversity, I forged my own path of least resistance, and gave myself a <i>chown</i> prototype that worked just the way I wanted. But none of this is really relevant to the matter at hand. The example code uses ffi because the real code used ffi and that's why.<p>The real problem is that <i>path</i> is a pointer to freed memory. The <i>CString</i> result burrito has already been consumed and recycled by the time we reach <i>chown</i>. This is bad.<p>This is also not a bug class I'm unfamiliar with. In the C++ days, one learned to be very careful around sloppy <i>c_str</i> usage lest a temporary string disappear from underneath you. So I'm aware of the disappearing temporary as a bug class, theoretically and practically. And I know rust has lots of temporary objects, and makes a big deal of object lifetimes, the whole deal. Probably could have figured this all out.<p>But still, I wasn't quite expecting this to happen. Assorted chains of unwrap, as_this, as_that, over_here, what_now are everywhere. The borrow checker keeps me safe. And this is <i>unsafe</i> code, but... <a href="https://steveklabnik.com/writing/you-can-t-turn-off-the-borrow-checker-in-rust">You can't "turn off the borrow checker" in Rust</a>. This isn't quite what Steve had in mind, I think, but to reiterate, I had two expectations. Borrow checker doesn't let temporaries disappear, and really, it doesn't. Elsewhere, I'd get all sorts of compiler errors (and helpful fix it hints) whenever something bad would happen. I mostly became accustomed to writing code in this fashion.<p>Nevertheless, tragedy struck. I was graciously informed of the error, and looked into it a bit to see if there was a better way to avoid it.<p>My first inclination was that I was lazy and had made the <i>unsafe</i> block too large.<p><pre><code><span class=kw>fn</span> gift<span class=op>(</span>filename<span class=op>:</span> <span class=tp>&String</span><span class=op>,</span> userid<span class=op>:</span> <span class=tp>i32</span><span class=op>)</span> <span class=op>{</span>
94 <span class=kw>let</span> path <span class=op>=</span> <span class=tp>CString::</span><span class=bi>new</span><span class=op>(</span>filename<span class=op>.</span><span class=bi>as_str</span><span class=op>()).</span><span class=bi>unwrap</span><span class=op>().</span>as_ptr<span class=op>()</span>;
95 <span class=kw>let</span> none <span class=op>=</span> <span class=op>-</span><span class=nm>1</span>;
96 <span class=bi>unsafe</span> <span class=op>{</span>
97 chown<span class=op>(</span>path<span class=op>,</span> userid<span class=op>,</span> none<span class=op>)</span>;
98 <span class=op>}</span>
99 <span class=op>}</span></code></pre><p>Surely this will elicit a compiler error? Nope. I was surprised to discover that <i>CString::as_ptr</i> is not unsafe. It gives you a poisoned pointer that can't be touched except in unsafe, but the call itself is perfectly safe. ish.<p>The fix is to write it correctly, of course.<p><pre><code><span class=kw>fn</span> gift<span class=op>(</span>filename<span class=op>:</span> <span class=tp>&String</span><span class=op>,</span> userid<span class=op>:</span> <span class=tp>i32</span><span class=op>)</span> <span class=op>{</span>
100 <span class=kw>let</span> path <span class=op>=</span> <span class=tp>CString::</span><span class=bi>new</span><span class=op>(</span>filename<span class=op>.</span><span class=bi>as_str</span><span class=op>()).</span><span class=bi>unwrap</span><span class=op>()</span>;
101 <span class=kw>let</span> none <span class=op>=</span> <span class=op>-</span><span class=nm>1</span>;
102 <span class=bi>unsafe</span> <span class=op>{</span>
103 chown<span class=op>(</span>path<span class=op>.</span>as_ptr<span class=op>(),</span> userid<span class=op>,</span> none<span class=op>)</span>;
104 <span class=op>}</span>
105 <span class=op>}</span></code></pre><p>What disturbs me about this solution is that it doesn't look obviously better. If anything, let's pack all the unwrap, as_this, as_that, over_here, what_now onto a single line so that we end up with just what we want seems like the more rusty way to do things.<p>The documentation does specifically call out this error. I'm pretty sure I read it. But I think there was a delay between reading the documentation, in which I wrote some other code, then came back to write this code and kinda winged it.<br><h4>clippy</h4><p>I was also informed that <b>clippy</b> will find and report this error. It does.<p>It also complains about a million other things, including the fact that I write numbers as 1000000 and not 1_000_000. There's something about errors and warnings, but some of my other stylistic atrocities like single iteration loops are marked as errors as well. I used it for a while, but generally found it too tiresome.<p>If there's an error which is a clear cut bug, I think it should be reported by an error detecting tool, not a linter. In the case of rust, that may be the compiler. Or a better tuned version of clippy. Given the focus of rust, it just feels like this error slipped between the cracks of should have been caught by the compiler and actually caught by the thing that tells me I'm not allowed to use w, x, y, and z variable names.<p>(In C, the compiler will let you get away with murder, but the compiler doesn't purport to be a life saving device, either.)<br><h4>go</h4><p>And now for the inevitable comparison to <b>go</b>. I have not had occasion to ffi my own <i>chown</i> in go because it's included in the stdlib, conveniently prototyped with signed ints so -1 just works, but I have done so to get access to functions like <i>unveil</i>. For consistency, I'll just stick with writing <i>gift</i> to use <i>cgo</i> to call <i>chown</i>.<p><pre><code><span class=cm>/*
106 #include <stdlib.h>
107 int chown(const char *, int, int);
108 */</span>
109 <span class=bi>import</span> <span class=st>"C"</span>
110
111 <span class=kw>func</span> gift<span class=op>(</span>filename <span class=tp>string</span><span class=op>,</span> userid <span class=tp>int</span><span class=op>)</span> <span class=op>{</span>
112 path <span class=op>:=</span> C<span class=op>.</span>CString<span class=op>(</span>filename<span class=op>)</span>
113 C<span class=op>.</span>free<span class=op>(</span>unsafe<span class=op>.</span>Pointer<span class=op>(</span>path<span class=op>))</span>
114 C<span class=op>.</span>chown<span class=op>(</span>path<span class=op>,</span> C<span class=op>.</span><span class=tp>int</span><span class=op>(</span>userid<span class=op>),</span> <span class=op>-</span><span class=nm>1</span><span class=op>)</span>
115 <span class=op>}</span></code></pre><p>This contains the same bug, freeing <i>path</i> before calling <i>chown</i>. But I think the error stands out a bit more. It should be obvious, even to casual reviewers, that calling <i>free</i> here is bad.<p><pre><code><span class=kw>func</span> gift<span class=op>(</span>filename <span class=tp>string</span><span class=op>,</span> userid <span class=tp>int</span><span class=op>)</span> <span class=op>{</span>
116 path <span class=op>:=</span> C<span class=op>.</span>CString<span class=op>(</span>filename<span class=op>)</span>
117 <span class=bi>defer</span> C<span class=op>.</span>free<span class=op>(</span>unsafe<span class=op>.</span>Pointer<span class=op>(</span>path<span class=op>))</span>
118 C<span class=op>.</span>chown<span class=op>(</span>path<span class=op>,</span> C<span class=op>.</span><span class=tp>int</span><span class=op>(</span>userid<span class=op>),</span> <span class=op>-</span><span class=nm>1</span><span class=op>)</span>
119 <span class=op>}</span></code></pre><p>The fixed code looks a lot like other go code I write. We use <i>defer</i> to cleanup http.Response.Body, sql.Rows, os.File, etc. And so, at a glance, I can tell the difference between the corrected version and the mistaken version. I use the same idiomatic style in both safe and unsafe code.<p>The go version is also susceptible to other errors, like completely failing to free the allocated string. I would like to believe this is avoidable with a little practice. The use of <i>defer</i> certainly makes it easy to cover every path, and it's a familiar pattern.<br><h4>thoughts</h4><p>I don't know how confident I am that I won't make the same error again.<p>Reviewing the rest of the code, I did use <i>CString</i> without error in several other places, but in that one instance I put the <i>as_ptr</i> right there with the constructor. There's no conscious reason for the difference, and if I hadn't been aware of the trouble it can cause, I may even have tidied up the good calls to look like the bad call.<p>Elsewhere, there's code like <code>captures.get(1).unwrap().as_str().to_string()</code> where I just kept tacking accessors onto the end until I got what I wanted. This code is admittedly hastily written and would not be my entrant for a programming pearls contest, but it mostly reads ok I think. I read rust code like this, I write code like this, it compiles, it works, everything is fine.<p>When I went back to read the documentation for <i>as_ptr</i> again, it seemed very familiar, so it's plausible I wrote the first few instances correctly and carefully. Then when it came time to write the problematic instance, I used my own existing code as a guide, and tuned it up a bit as I retyped it to more closely resemble the code I'd written in between.<p>And that's what troubles me. I learn this is normal, but then there are certain magic situations where it's not normal and different rules apply. Or rather the same rules, but now they matter in a different way. There's a feeling like I was lulled into false sense of security. The borrow checker works very well, I expect it to keep working, and it even does in unsafe blocks, just unexpectedly not for this function.<p>Even now, aware of the problem, I can think to myself that surely next time I'll look out for this, but I also know it usually takes a few lessons before the learning is done. It would be optimistic to assume I'll never blink and slide right past this error again.<p>In fairness, I should add that the error was pointed out to me by several people, in quick succession, so it's entirely possible for reviewers attuned to it to spot it more readily.<p>The go approach to cgo and unsafety certainly has more failure modes. And less tooling assistance, since go vet won't warn about using a freed pointer. Nevertheless, just glancing at the incorrect version of the function, the error pops out at me. This may be partially due to familiarity, but I don't believe that fully explains it.<p>I can see the error in the go code, because the error is right there to see. <b>The error is in the code.</b> The error in the rust code isn't in the code. It's in the invisible spaces, the gaps between the code. It's a subtle difference, but it feels significant.<br><h4>ps</h4><p>The error would have been detected far sooner had I not been lazy and checked the return value of <i>chown</i> (for a file not found error). But I was running as root, knew the file existed, and wasn't taking things too seriously. Why even bother checking? So score one for team check every function for failure. That's my usual team, except when it isn't.<p>I'm also curious if the mistake would have stung nearly so much if I'd found it immediately instead of putting it out there for somebody else to find. I suspect I would have quickly dismissed it as just another quirk of how things are done, and failed to learn the bigger lesson that this is something that one should be vigilant in guarding against. Teaching mode requires the stove be preheated to the optimal temperature.]]></description>
120 <category>go</category>
121 <category>programming</category>
122 <category>rust</category>
123 <link>https://flak.tedunangst.com/post/comparative-unsafety</link>
124 <pubDate>Mon, 17 Aug 2020 13:53:03 UTC</pubDate>
125 <guid isPermaLink="true">https://flak.tedunangst.com/post/comparative-unsafety</guid>
126 </item>
127 </channel>
128 </rss>