<QUOTE author="gijoe77" post_id="1611" time="1532094391" user_id="243">
gijoe77 post_id=1611 time=1532094391 user_id=243 Wrote:I actually wrote out a quick algorithm (with pencil and paper while at work) ...
Kudos for this one.
<QUOTE author="gijoe77" post_id="1611" time="1532094391" user_id="243">
gijoe77 post_id=1611 time=1532094391 user_id=243 Wrote:also, about the free() part - I was under the impression its good practice to do a free after a malloc to avoid memory leaks... so the context of what your talking about is more of a security coding aspect or I would be introducing some sort of bug? I guess what I mean is I don't understand the gravity of creating a "automatic variable on stack".. thanks again for your time and feedback
I'll try to elaborate a bit on this one:
When using putenv(), what you actually do is:
1. Calculate size of the environ array of pointers
2. Create new environ array of pointers, say new_environ, with +1 entry (for the new "envname=envval" string to be added)
3. Copy pointers from environ array to new_environ array (which now includes the new string)
4. Update environ global variable with address of new_environ
The new environ array, new_environ, is created using malloc or realloc. The space for the new "envname=envval" string, is allocated using malloc too. For this second space to persist throughout lifetime of the process, the buffer/space should not be freed.
If you'd free up this buffer, this same buffer could be reused as soon as another malloc would be called, thus overwriting the "envname=envval" string. Exactly when this will happen depends on the implementation of malloc and of the buffer size you are about to allocate.
The reason your program runs fine even using free() after putenv(), is because after your free() the program does not make enough use of malloc/realloc/calloc for this same buffer to be allocated again. That's the only reason the new entry is still there in its entirety.
Ok, so what about this other "automatic variables"-thing?
When calling a function in C, for example, you allocate space for local variables:
Code:
<i>
</i>int main()
{
int a; // <- local
char *s; // <- local
static int c; // <- not local
extern char *environ; // <- not local
.
.
.
}
Nonlocal variables have their space physically allocated somewhere externally to main (in some data section of the process, accessible through global pointers, for example). OTOH, local variables have their space physically allocated on a memory region called stack, which works as follows:
When a function is called, stack space is allocated to provide space for the local variables. When this same function is exited, the *same* stack space is deallocated. The operations of allocate/deallocate are reduced to a simple increase (subtract from stack address pointer) or decrease (add to stack address pointer). You increase by subtracting because most of the time the stack increases towards lower addresses. Note this is just a convention (PA-RISC for example has their stack growing through higher addresses, so it's exactly the opposite).
An example for Tru64/Alpha:
Code:
<i>
</i>#include <stdio.h>
int main(int argc, char *argv[])
{
printf("Hello\n");
}
Would produce the following stack allocations/deallocations:
Code:
<i>
</i># dis -p main hello
main:
0x120001140: 27bb2000 ldah gp, 8192(t12)
0x120001144: 2ffe0000 ldq_u zero, 0(sp)
0x120001148: 23bd6f30 lda gp, 28464(gp)
0x12000114c: 2ffe0000 ldq_u zero, 0(sp)
0x120001150: 23defff0 lda sp, -16(sp) <-- increase stack space
...
0x120001174: 23de0010 lda sp, 16(sp) <-- decrease stack space
0x120001178: 23bd6f08 lda gp, 28424(gp)
0x12000117c: 2ffe0000 ldq_u zero, 0(sp)
0x120001180: 6bfa8001 ret zero, (ra), 1
#
When adding more local variables:
Code:
<i>
</i>#include <stdio.h>
int main(int argc, char *argv[])
{
int i;
int int_array[100];
for (i = 0; i < 100; i++)
int_array[i] = i;
printf("Hello %i %i %i\n", int_array[0], int_array[1], int_array[2]);
}
# dis -p main hello
main:
0x120001100: 27bb2000 ldah gp, 8192(t12)
0x120001104: 2ffe0000 ldq_u zero, 0(sp)
0x120001108: 23bd6f70 lda gp, 28528(gp)
0x12000110c: 2ffe0000 ldq_u zero, 0(sp)
0x120001110: 23defe60 lda sp, -416(sp) <-- 16 + int_array (100 * 4) = 416
...
0x120001160: 23de01a0 lda sp, 416(sp) <-- decrease it again
0x120001164: 23bd6f1c lda gp, 28444(gp)
0x120001168: 6bfa8001 ret zero, (ra), 1
#
You may wonder why without having declared any local variable in the first example, the stack is being grown by 16. Well, there are other pieces of information saved on each function call, that require, in this case, at least 16 bytes of stack space.
And what about automatic variables, then? Local variables that get allocated on this stack space are called automatic variables because they exist after you increase stack space (function entry) and cease to be valid after you decrease the same stack space (function exit), without any intervention whatsoever of the program writer itself. It just happens *automatically* through the aformentioned machine instructions added by the compilers to each function.
<B>
putenv() + automatic variable</B>
<B>
-------------------------------------</B>
Since automatic variables are so short lived, if you use them for putenv() you can easily overwrite the passed string on subsequent function calls:
Code:
<i>
</i>#include <stdio.h>
#include <stdlib.h>
void A()
{
/* A()'s local variable s */
char s[15] = "MYNAME=MYVALUE";
putenv(s);
printf("A(): getenv(\"MYNAME\") = \"%s\"\n", getenv("MYNAME"));
printf("A(): Address of s = %p\n", s);
}
void B()
{
/* B()'s local variable is now at the same address as A()'s before !!! */
char s[15] = "MYNAME=HIJKLMN";
printf("B(): overwrite with s = %s\n", s);
printf("B(): getenv(\"MYNAME\") = \"%s\"\n", getenv("MYNAME"));
printf("B(): Address of s = %p\n", s);
}
int main(int argc, char *argv[])
{
A();
B();
}
# ./test
A(): getenv("MYNAME") = "MYVALUE"
A(): Address of s = 11fffbfd8
B(): overwrite with s = MYNAME=HIJKLMN
B(): getenv("MYNAME") = "HIJKLMN"
B(): Address of s = 11fffbfd8
#
The same applies for a putenv() call with a malloced string that is freed afterwards:
Code:
<i>
</i>#include <stdio.h>
#include <stdlib.h>
int main()
{
int i, j;
char *s;
char *p;
s = (char *) malloc(15);
strcpy(s, "MYNAME=MYVALUE");
putenv(s);
/* No problem here */
printf("getenv(\"MYNAME\") = \"%s\"\n", getenv("MYNAME"));
free(s); /* <-- Now subsequent mallocs can reuse the same buffer! */
/* s still exists but can be reused now */
printf("getenv(\"MYNAME\") = \"%s\"\n", getenv("MYNAME"));
/* Repeat often/long enough to trash previous buffer location */
/* NOTE: Depends on malloc implementation */
for (i = 0; i < 1000; i++)
{
p = (char *) malloc(15);
/* Fill new buffers with garbage */
for (j = 0; j < 15; j++)
p[j] = 'd'; /* You can put anything here */
free(p);
}
/* Let's see what's left of our environment string */
printf("getenv(\"MYNAME\") = \"%s\"\n", getenv("MYNAME"));
}
# ./test2
getenv("MYNAME") = "MYVALUE"
getenv("MYNAME") = "MYVALUE"
getenv("MYNAME") = "(null)"
#
When commenting out the free(), everything is fine:
Code:
<i>
</i># ./test2
getenv("MYNAME") = "MYVALUE"
getenv("MYNAME") = "MYVALUE"
getenv("MYNAME") = "MYVALUE"
#
Hope this helps.
Tru