requesting help with setenv/unsetenv

If you're coding on or porting to IRIX, this is the forum for discussion.
User avatar
gijoe77
Posts: 215
Joined: Fri Jun 22, 2018 9:17 pm

Re: requesting help with setenv/unsetenv

Post by gijoe77 » Fri Jul 20, 2018 9:46 am

very nice Tru, thanks for sharing, I will be studying what you coded - I actually wrote out a quick algorithm (with pencil and paper while at work) of how I was going to implement the overwrite functionality so it will be interesting for me to see how you did it vs how I was approaching it

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

TruHobbyist
Posts: 25
Joined: Tue May 15, 2018 12:04 am

Re: requesting help with setenv/unsetenv

Post by TruHobbyist » Fri Jul 20, 2018 1:35 pm

gijoe77 wrote:
Fri Jul 20, 2018 9:46 am
I actually wrote out a quick algorithm (with pencil and paper while at work) ...
Kudos for this one.
gijoe77 wrote:
Fri Jul 20, 2018 9:46 am
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: Select all

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: Select all

#include <stdio.h>

int main(int argc, char *argv[])
{
        printf("Hello\n");
}
Would produce the following stack allocations/deallocations:

Code: Select all

# 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: Select all

#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.

putenv() + automatic variable
-------------------------------------
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: Select all

#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: Select all

#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: Select all

# ./test2
getenv("MYNAME") = "MYVALUE"
getenv("MYNAME") = "MYVALUE"
getenv("MYNAME") = "MYVALUE"
#

Hope this helps.

Tru

User avatar
gijoe77
Posts: 215
Joined: Fri Jun 22, 2018 9:17 pm

Re: requesting help with setenv/unsetenv

Post by gijoe77 » Sun Jul 22, 2018 12:01 am

thanks Tru,

I will be an vacation this week with my family so I will be limited in what I can do, but I will re-read this a few times and really try to get a good handle on it. Thanks for your time on this topic

TruHobbyist
Posts: 25
Joined: Tue May 15, 2018 12:04 am

Re: requesting help with setenv/unsetenv

Post by TruHobbyist » Sun Jul 22, 2018 5:32 am

You're welcome.

Happy holidays

Post Reply