Rust is supposed to guarantee statically that a reference cannot outlive its referent. The `T: 'static` bound means that the value that Q holds must have a 'static lifetime (e.g. either be an owned value or a reference to a static value). So this program should not compile since a reference to l is not 'static. This code is unsound because rust will drop the string l before q so when the Q destructor runs the underlying string has already been freed yet we have a valid reference to it.
$ cat main.c
#include <stdlib.h>
#include <string.h>
struct Q {
/*@null@*/ /*@only@*/ char * t;
};
/*@only@*/
struct Q *
Q_new (/*@only@*/ char * t)
{
struct Q * q = malloc (sizeof *q);
q->t = t;
return q;
}
void
Q_set (struct Q * q, /*@only@*/ char * t)
{
free (q->t);
q->t = t;
return;
}
void
Q_free (/*@only@*/ struct Q * q)
{
free (q->t);
free (q);
return;
}
int
main (void)
{
/*@only@*/ char * l;
/*@only@*/ struct Q * q;
l = strdup ("");
q = Q_new (NULL);
Q_set (q, l);
free (l);
/* This is UB */
Q_free (q);
return EXIT_SUCCESS;
}
$ splint main.c
Splint 3.1.2 --- 21 Feb 2021
main.c: (in function Q_new)
main.c:15:3: Arrow access from possibly null pointer q: q->t
A possibly null pointer is dereferenced. Value is either the result of a
function which may return null (in which case, code should check it is not
null), or a global, parameter or structure field declared with the null
qualifier. (Use -nullderef to inhibit warning)
main.c:14:17: Storage q may become null
main.c: (in function main)
main.c:43:6: Unrecognized identifier: strdup
Identifier used in code has not been declared. (Use -unrecog to inhibit
warning)
main.c:44:13: Null storage passed as non-null param: Q_new (NULL)
A possibly null pointer is passed as a parameter corresponding to a formal
parameter with no /*@null@*/ annotation. If NULL may be used for this
parameter, add a /*@null@*/ annotation to the function parameter declaration.
(Use -nullpass to inhibit warning)
main.c:47:8: Dead storage l passed as out parameter to free: l
Memory is used after it has been released (either by passing as an only param
or assigning to an only global). (Use -usereleased to inhibit warning)
main.c:46:12: Storage l released
main.c:12:1: Function exported but not used outside main: Q_new
A declaration is exported, but not used outside this module. Declaration can
use static qualifier. (Use -exportlocal to inhibit warning)
main.c:17:1: Definition of Q_new
main.c:20:1: Function exported but not used outside main: Q_set
main.c:25:1: Definition of Q_set
main.c:28:1: Function exported but not used outside main: Q_free
main.c:33:1: Definition of Q_free
Finished checking --- 7 code warnings
If you wrote a Q_free function and called it after `free(l);` to actually demonstrate the UAF, that would be pretty close to the semantics of the Rust example.
I originally didn't do this, because I didn't feel the need to invoke UB without necessity. Also I wanted to demonstrate, that the error can (and is) caught in C, without actually committing the double-free (so that the program behaviour is still well-defined, but the abstraction is already violated).
I'm not familiar with Splint, but my understanding is that the error is because of the @only@ marker indicating that the string is not shared. That's an incorrect translation of the Rust program, where the reference to the string is explicitly a shared reference rather than an owned reference. The issue isn't the sharing; it's that rustc mistakenly allows the code to assign a reference that is valid until the end of the current scope to a field that expects a reference valid forever (like a string constant in the program binary). The Rust program with equivalent semantics to your C program (passing an owned String instead of a shared reference) is correctly rejected by the compiler.
Also, the Q_free function should not free q->t. It may access it (e.g. printing it), but it does not own that reference and is not responsible for deallocating its memory.
Sorry, I should have looked closer the first time! My excuse is that it was too early in the morning to be thinking about such things.
Yes, @only@ means that the parameter/variable is owning. I believed that would have been the meaning in Rust. Now I can no longer edit my comment, oh well.
> Also, the Q_free function should not free q->t.
Well, that's because I thought it would own it. But if in Rust it is not implied that the Q owns the string, then I fail to see where the error is? Both l and q will be deallocated at the end of the main function. Depending on the order of the deallocation maybe q exists for a theoretical time while pointing to already deallocated memory. But both get deallocated at the same point, so q does not outlive the existence of l, so where is the error?
The error is the destructor of `q` could look at the reference and stash it away somewhere, like a global variable (since the compiler thinks the reference is valid forever). Then, some other part of the program could access that variable and cause a UAF.
So Q is a "tuple-struct". In rust you can define a struct like
struct Q<T> {
value: Option<T>
}
with a named field or you can define it like
struct<T> Q(Some<T>)
which is the same struct layout but without an explicit name for the value. In that case, the field is implicitly names `0`
So q.0 = Some(foo) is equivalent to q.value = Some(foo).
The Some(..) is just one variant of the Option enum:
enum Option<T> {
None,
Some(T)
}
Option represents a value that may or may not be initialized. For uninitialized values you use the None variant and then you can initialize it to Some(value).
Some(...) is just constructor for an option type as seen in other languages e.g. java's Optional, haskell's Maybe, c++'s std::optional. I assume that it's needed here because the field can't start as a null pointer otherwise. Maybe the vulnerability still works if the field starts with one string value and swaps to a different string value, but having it start holding None made for a simpler demo.
q.0 is the first field of q, which is anonymous. They could have just as well called it x and written q.x.
That's incorrect; the compiler drops q before l. Drops happen in LIFO order; and the compiler knows that it can't drop l before q has been dropped, because q contains a reference to l.
The issue is that the destructor of q can observe a reference which is about to be deallocated as having a 'static lifetime (i.e. it will live forever), and can thus stash it away somewhere for other code to access after it's been freed (as in the examples in the issue which demonstrate UB.)
My thoughts as well. The problem can be alleviated either by making the ownership change an error or "just" invoking the destructors in the correct order.
A 'static lifetime constraint on T says: types that satisfy T must not contain any borrowed value/content whose lifetime will be shorter than the life of the program.
So the compiler assumes it is guaranteed, that 'l' still exists, so it doesn't consider it in regard of finalizer ordering.
I'm not entirely sure "general" intelligence is a meaningful concept but I take judgement to mean something like "the ability to make decisions under uncertainty without direct feedback." People seem to be able to do this using a lot of context and tacit knowledge. It's not really clear how you hill-climb to that capability through RL.
> the history of artificial intelligence research is littered with examples of humans confidently declaring that task X requires general intelligence, then getting humiliated by a neural network doing task X better than humans a few years later
True, but the history of AI research is also littered with AI researchers confidently predicting X job will be replaced by AI and being completely wrong because they don't actually understand what those jobs actually are. See Geoff Hinton predicting that Radiologists would be obsolete by ~2020, or predictions that truck drivers would all be replaced by self-driving tech.
I think all this debate about whether an LLM can write (or download) a chess engine is sort of missing the point. For basically any economically valuable work there is no equivalent of a chess engine for it. If there were we wouldn't need humans or AI to begin with.
It is pretty common to be prompted to tip by the PoS system in any place you can buy made-to-order food. This includes stores that are normal retail stores but also have some sort of made-to-order food. Like a fancy wine shop or gourmet food store that also sells sandwiches. But this isn't really a new phenomenon either. It's just that there used to be a tip jar sitting by the register, but now the PoS system prompts you because nobody carries cash anymore.
> You get better at modeling the trajectory because the neurons physically move closer together as you learn
I think "modeling the trajectory" is not necessarily what we are doing either. It's more likely we are using much simpler heuristics. If you are trying to catch a ball flying through the air, you can just look at the ball and modulate your running speed to keep your eyes at a fixed angle until you catch the ball. It's much more analogous to a PID controller than a model of the trajectory.
The brain doesn't process vision anywhere near fast enough for some of the stuff you see in fast sporting actions if it were purely reactive - batting in baseball/cricket, returning a table-tennis shot, etc. In some cases the time available for movement is extremely restricted and yet people can respond accurately. We may not know exactly how the brain implements it, but it clearly uses prediction to estimate where the object is going to be - i.e. it is, in some sense, modelling its trajectory.
> LLM-generated code has a tendency to “over-justify”
This is the thing that drives my nuts about LLM-generated code. I will see PRs that fix a bug and the entire bug fix is re-explained in 10 different places in a code comment.
> 55% chance for a $10 payout; 45% chance to lose $10. You would keep on rolling that die, right?
The answer is that it depends? If I have $1B then yeah I would roll the dice and keep rolling as long as they let me. If I have $100? Maybe not because you can go very easily go broke even if each bet is positive EV.
I'm in my early forties and both my parents worked (as did all my neighborhood friend's parents) and we still spent a lot of time wandering around. Honestly I think people are really overthinking this. We spent a lot of time wandering around outside because we were bored. Now kids have an endless well of entertainment to choose from so staying at home is a much more appealing option. It's always tempting to romanticize your childhood but if I'm being honest, most of that time wandering around outside I was bored out of my skull. I was just marginally less bored than I would have been sitting at home.
reply