Is TOKEN_USER losing its memory allocation?

58 views
Skip to first unread message

Daniel Widdis

unread,
Mar 10, 2021, 12:42:01 PM3/10/21
to Java Native Access
I *think* this is a bug but want another set of eyes on this to confirm before filing an issue (and/or PR to fix).


Reproduction: nearly always after querying process tokens for a period of time on a user's AWS EC2 machine running Windows Server 2016, in code run as SYSTEM, evaluating a token on processes owned by either SYSTEM or Administrator.

Symptom (Line numbers are from 5.7.0 release):

com.sun.jna.platform.win32.Win32Exception: The parameter is incorrect. at
com.sun.jna.platform.win32.Advapi32Util.getAccountBySid(Advapi32Util.java:285) at
com.sun.jna.platform.win32.Advapi32Util.getAccountBySid(Advapi32Util.java:251) at
com.sun.jna.platform.win32.Advapi32Util.getTokenAccount(Advapi32Util.java:548) 

Instrumented troubleshooting indicates odd values for the PSID parameter passed from getTokenAccount to getAccountBySid:
 - in one case, the SID is null (PSID is a pointer to null)
 - in another case, the pointer value pointed to by PSID changes between two consecutive (failed) calls to LookupAccountSid.

These symptoms prompted me to look up how the memory for the PSID.  We fetch the required token length and allocate with;

WinNT.TOKEN_USER user = new WinNT.TOKEN_USER(tokenInformationLength.getValue());

Looking into TOKEN_USER, I see the memory allocation here:

        public TOKEN_USER(int size) {
            super(new Memory(size));
        }

The superclass (Structure) only tracks the pointer to that Memory object, assuming it's been allocated on the native side.  But after calling the constructor here, Memory is available for GC and release of its native allocation.

I believe the correct fix here would be to store a separate private Memory object in the class to maintain the reference.  

I'm unclear why this problem would only (or at least primarily) present in the case of users SYSTEM or Administrator, but I suppose they might have a much larger SID memory allocation requirement than other users, and thus be more likely to be recycled.

Can anyone confirm my observations here?

Matthias Bläsing

unread,
Mar 10, 2021, 2:01:58 PM3/10/21
to jna-...@googlegroups.com
Hi,

Am Mittwoch, den 10.03.2021, 09:42 -0800 schrieb Daniel Widdis:
> [Strange behavior of Advapi32Util.getTokenAccount]
>
> The superclass (Structure) only tracks the pointer to that Memory
> object, assuming it's been allocated on the native side.  But after
> calling the constructor here, Memory is available for GC and release
> of its native allocation.
>
> I believe the correct fix here would be to store a separate private
> Memory object in the class to maintain the reference.  
>
> I'm unclear why this problem would only (or at least primarily)
> present in the case of users SYSTEM or Administrator, but I suppose
> they might have a much larger SID memory allocation requirement than
> other users, and thus be more likely to be recycled.
>
> Can anyone confirm my observations here?

this sounds sane. I'm sure I don't like the suggested fix.

The issue roots deeper: JNA IMHO makes the wrong assumption, that you
could handle native memory with a GC. If I remember correctly project
panama (the potential JNI successor) uses a scoped approach, but is
still shuffeling the API.

The simplest fix would be to ensure, that the memory stays referenced.

With JDK 9+ this should work:

public static Account getTokenAccount(HANDLE hToken) {
// ...
try {
return getAccountBySid(user.User.Sid);
} finally {
Reference.reachabilityFence(user.getPointer());
}
}

I would also expect, that this should work:

public static Account getTokenAccount(HANDLE hToken) {
// ...
try {
return getAccountBySid(user.User.Sid);
} finally {
user.getPointer().getByte(0);
}
}

Greetings

Matthias


Daniel B. Widdis

unread,
Mar 10, 2021, 3:06:16 PM3/10/21
to Java Native Access
The suggested fix doesn't really work, anyway, as super() must be the first call in the constructor and there seems to be no easy way to do a non-static size based memory allocation stored in a class field even inline in the super() call.  Given JNA will not be jumping to JDK9+ support, it looks like your second fix (or some variation of it) looks more promising.

But it looks like the super(new Memory(size)) pattern is in quite a few other class constructors as well, and I think I'd prefer a fix in these objects themselves rather than requiring the user to jump through hoops to track the memory.

Looking at Structure it seems that it can (and does) track its own memory allocation, which can be changed.  My initial suggestion coupled with useMemory() could in theory work, but it seems even easier.  Wouldn't something of this form work?

        public TOKEN_OWNER(int size) {
            super();
            super.allocateMemory(size);
        }


--
You received this message because you are subscribed to the Google Groups "Java Native Access" group.
To unsubscribe from this group and stop receiving emails from it, send an email to jna-users+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/jna-users/5d9d48bfb6c3f80bcb0787a49f70d2cad24f2ec6.camel%40doppel-helix.eu.


--
Dan Widdis

Matthias Bläsing

unread,
Mar 11, 2021, 1:41:17 PM3/11/21
to jna-...@googlegroups.com
Hi,

I don't think this will work. Here is what I think is happening:

- Memory is allocated in https://github.com/java-native-access/jna/blob/master/contrib/platform/src/com/sun/jna/platform/win32/Advapi32Util.java#L538
It does not matter whether the memory is allocated with new Memory
or allocateMemory. The latter only clears the Memory prior to
handing it out.
- I assume, that the SID returned in the structure is located inside
the Memory object
- the SID is used two times
- once without error: https://github.com/java-native-access/jna/blob/5.7.0/contrib/platform/src/com/sun/jna/platform/win32/Advapi32Util.java#L268
- again with error: https://github.com/java-native-access/jna/blob/5.7.0/contrib/platform/src/com/sun/jna/platform/win32/Advapi32Util.java#L283

The only difference between the two calls is, that allocations happen in between:

https://github.com/java-native-access/jna/blob/5.7.0/contrib/platform/src/com/sun/jna/platform/win32/Advapi32Util.java#L280-L281

The object allocated in the first step is already out of reach, so it can be GCed.

We are back to the requirement, that a strong reference to Memory/the original TOKEN_USER needs to be retained.

Greetings

Matthias

Tres Finocchiaro

unread,
Mar 11, 2021, 1:57:48 PM3/11/21
to jna-...@googlegroups.com
As a stop-gap, would it make sense to use reflection to favor the Java 9 technique, if available?

Matthias Bläsing

unread,
Mar 11, 2021, 2:17:23 PM3/11/21
to jna-...@googlegroups.com
Hi,

Am Donnerstag, den 11.03.2021, 13:57 -0500 schrieb Tres Finocchiaro:
> As a stop-gap, would it make sense to use reflection to favor the
> Java 9 technique, if available?

no need - a JDK 6+ solution was already presented and is implemented
here - it would be great if it could be tested:

https://github.com/matthiasblaesing/jna/commit/8f4685bf37abf29630621a7eaae023de753e65c5

Greetings

Matthias

Daniel B. Widdis

unread,
Mar 11, 2021, 3:03:08 PM3/11/21
to Java Native Access
>   It does not matter whether the memory is allocated with new Memory or allocateMemory. The latter only clears the Memory prior to handing it out

I think there is a difference.

In the existing code, we create the TOKEN_USER structure user.

        WinNT.TOKEN_USER user = new WinNT.TOKEN_USER(tokenInformationLength.getValue());

This invokes this constructor:

        public TOKEN_USER(int size) {
            super(new Memory(size));
        }

So a new Memory object is created, but only its Pointer is passed to the superclass (Structure) constructor

    /** Create a structure cast onto pre-allocated memory. */
    protected Structure(Pointer p) {
        this(p, ALIGN_DEFAULT);
    }

As the comment notes, this assumes memory allocated elsewhere. This eventually goes here:

        if (p != null) {
            useMemory(p, 0, true);
        }

With the comments:

    /** Set the memory used by this structure.  This method is used to
     * indicate the given structure is based on natively-allocated data,
     * nested within another, or otherwise overlaid on existing memory and
     * thus does not own its own memory allocation.
     * @param m Native pointer
     * @param offset offset from pointer to use
     * @param force ByValue structures normally ignore requests to use a
     * different memory offset; this input is set <code>true</code> when
     * setting a ByValue struct that is nested within another struct.
     */
    void useMemory(Pointer m, int offset, boolean force) { ... }

So only the Pointer is stored and it is assumed the native allocation is tracked/released elsewhere.  No memory bounds checking is done. In fact, it is released when the Memory object, only used inline in the TOKEN_USER constructure, is GC'd (finalize() -> dispose() -> free(peer)).  Early on with no pressure on the heap, this isn't a problem as GC is delayed, but when heap limited, GC is more aggressive and I would guess tries to free larger allocations first.

With my suggestion, we maintain a reference to the memory as follows:

        public TOKEN_USER(int size) {
            super();
            allocateMemory(size);
        }

This invokes the no-arg superclass constructor:

    protected Structure() {
        this(ALIGN_DEFAULT);
    }

Which eventually leads to the "else" branch because p is null:

        if (p != null) {
            useMemory(p, 0, true);
        }
        else {
            allocateMemory(CALCULATE_SIZE);
        }

This analyzes the existing fields in TOKEN_USER (a Pointer and an Int) and autoallocates memory for them, storing it in the Structure class's memory field (private Pointer memory;) because the first time we get here, that field is null:

            if (this.memory == null
                || this.memory instanceof AutoAllocated) {
                this.memory = autoAllocate(size);
            }

At this point, memory contains an AutoAllocated object with enough room for the Pointer and Int.

Now, we reach the second line and explicitly call allocateMemory with our larger size (that contains the characters/strings for the SID info).  This time around, we hit the "instanceof AutoAllocated" conditional and again call autoAllocate, but with the bigger size, replacing the default allocation with the one we need.  Plus, the memory field keeps a reference to it as long as the TOKEN_USER structure exists.





--
Dan Widdis

Daniel B. Widdis

unread,
Mar 11, 2021, 3:15:07 PM3/11/21
to Java Native Access
An alternative way of tracking the memory (per my initial suggestion) would be in TOKEN_USER itself as follows:

    @FieldOrder({"User"})
    public static class TOKEN_USER extends Structure {
        /**
         * Specifies a SID_AND_ATTRIBUTES structure representing the user
         * associated with the access token. There are currently no attributes
         * defined for user security identifiers (SIDs).
         */
        public SID_AND_ATTRIBUTES User;

        // Remember the memory allocation if size constructor is used
        private Pointer p;

        public TOKEN_USER() {
            super();
        }

        public TOKEN_USER(Pointer memory) {
            super(memory);
            read();
        }

        public TOKEN_USER(int size) {
            super();
            p = new Memory(size);
                      useMemory(p);
        }
    }

This may be preferable because the allocation isn't really "auto".  Whichever option we choose, there are multiple examples of super(new Memory(size)) that need to be fixed.
--
Dan Widdis

Matthias Bläsing

unread,
Mar 11, 2021, 3:17:56 PM3/11/21
to jna-...@googlegroups.com
This does not work. TOKEN_USER goes out of scope and after that the
memory _immediately_ eligable for GC.

The problem is, that our original Memory is tied to the TOKEN_USER, but
only PSID is retained. That PSID has no reference to the original
memory.

Greetings

Matthias

Matthias Bläsing

unread,
Mar 11, 2021, 3:27:34 PM3/11/21
to jna-...@googlegroups.com
Am Donnerstag, den 11.03.2021, 12:02 -0800 schrieb Daniel B. Widdis:
> So a new Memory object is created, but only its Pointer is passed to
> the superclass (Structure) constructor

Memory _is_ _a_ Pointer:

public class Memory extends Pointer {
...
}


So the _Memory_ object is passed and stays referenced. If that would
not happen, we would be even more sad. The problem is in this case,
that there is a additional indirection:

There is a PSID.ByReference in there. From the POV of JNA this is a
pointer and that pointer is read and a structure is wrapped around it.
That code can be found in Pointer#getVaue (line 367). The pointer of
the structure is found via:

Pointer#getPointer(offset)

that constructs a raw pointer like this:

return Native.getPointer(peer + offset);

so that does not retain a reference to the original memory object.

As an idea:

if Memory would override getPointer (and all methods of Pointer, that
return a Pointer objects), it could work like this:

- get the target pointer via the original method
- check if the new pointer points into the Memory object itself
- if the target of the pointer is inside the memory object, discard
the calculated value and replace it with a call to Memory#share

The Pointer/Memory object returnd by Memory#share holds a hard
reference to the Memory object it was created from and thus will
prevent GC of the original memory object.


I thought about creating a ManualMemory object, that is only freed,
then an explicit close method is called, but that won't work, as the
Struture object is hard tied to the GC based Memory object.


Greetings

Matthias

Daniel B. Widdis

unread,
Mar 11, 2021, 3:45:22 PM3/11/21
to Java Native Access
Ahhhhhhhhhhhh, I see it now.

I missed the significance of that call being the last one in the method and the impact of that on user.

I also hadn't traced through useMemory() enough to see that in the 0-offset case, it does indeed store the pointer which is still the memory object.

--
You received this message because you are subscribed to the Google Groups "Java Native Access" group.
To unsubscribe from this group and stop receiving emails from it, send an email to jna-users+...@googlegroups.com.


--
Dan Widdis

Durchholz, Joachim

unread,
Mar 12, 2021, 3:14:30 AM3/12/21
to jna-...@googlegroups.com

> Early on with no pressure on the heap, this isn't a problem as GC is delayed, but when heap limited, GC is more aggressive and I would guess tries to free larger allocations first.

 

Don’t even guess, switch on GC tracing and look at the traces.

 

For any OpenJDK-based JVM since 1.5, you have a copying collector.

Copying collectors never touch unreachable objects, they just ignore them and copy the reachable ones.

Now OpenJDK got a ton of detail optimization since 1.5 (and it wasn’t half bad even in 1.5), and I recall having seen that there’s some special treatment for “humongous” blocks; my assumption that unreachable large  blocks don’t get any special treatment may be wrong as well ;-)

 

Regards,

Jo

 

Sensitivity: C2 Internal

The content of this e-mail is intended only for the confidential use of the person addressed.
If you are not the intended recipient, please notify the sender and delete this e-mail immediately.
Thank you.

Daniel Widdis

unread,
Mar 12, 2021, 3:21:37 AM3/12/21
to jna-...@googlegroups.com

Given I’m a developer busy with a day job and supporting open source in my spare time, not having the equipment to reproduce this myself, and relying on the goodwill of users to do enough logging for me to try to identify the problem, this seems overkill.

 

But if you’d like to try to reproduce the problem and trace it to confirm my guess, I’d love the input!

--

You received this message because you are subscribed to the Google Groups "Java Native Access" group.
To unsubscribe from this group and stop receiving emails from it, send an email to jna-users+...@googlegroups.com.

Durchholz, Joachim

unread,
Mar 12, 2021, 3:24:10 AM3/12/21
to jna-...@googlegroups.com

Heh. Sorry. That was just a comment from the peanut gallery.

Same situation for me :-)

 

All I wanted to say that you shouldn’t base decisions on assumptions what GC is doing, that’s risky.

I’m not saying it’s important for the topic at hand :-)

 

 

Sensitivity: C2 Internal

Daniel Widdis

unread,
Mar 12, 2021, 3:35:55 AM3/12/21
to jna-...@googlegroups.com

All good.

 

I’m only really guessing at the symptoms.  But tracing through the code and identifying where references are lost are more deterministic ways of fixing it, which is what I tried to do, and Matthias did much better than I did!

 

I do appreciate the advice and wouldn’t be surprised if I end up using it for some other problem in the future.

Daniel Widdis

unread,
Mar 14, 2021, 1:42:46 PM3/14/21
to jna-...@googlegroups.com

OK, I'm still confused here. 

 

Our fix(es) are based on the idea that the variable "user" has gone out of scope in this line because we’re passing on a reference not attached to “user”:

 

return getAccountBySid(user.User.Sid);

 

Initially I thought this was associated with the method "ending" with the return call, like tail recursion.   However, as I've read more, Java does not do any optimization for tail recursion and every reference I see indicates scope lasts until the trailing curly brace.  I wrote this test class which seems to confirm the variable is still in scope throughout the return call:

 

public class Test {

 

    static boolean bar = false;

 

    static Foo foo = null;

 

    static class Foo {

        public int baz = 42;

 

        public Foo() {

            bar = true;

        }

 

        @Override

        protected void finalize() {

            bar = false;

        }

    }

 

    static boolean scopeTest() {

        foo = new Foo();

        System.out.println("Foo is " + foo + ", bar is " + bar);

        return tailCall(foo.baz);

    }

 

    private static boolean tailCall(int baz) {

        // has the scopeTest() method ended yet?

        System.gc();

        System.gc();

        Util.sleep(2000);

        System.gc();

        System.gc();

        System.out.println("Foo is " + foo + ", bar is " + bar);

        return bar;

    }

 

    public static void main(String[] args) {

        scopeTest();

    }

}

So despite our “fixes” apparently working, I still don’t understand exactly why Memory is losing the allocation in the return statement.

    --

    You received this message because you are subscribed to the Google Groups "Java Native Access" group.

    To unsubscribe from this group and stop receiving emails from it, send an email to jna-users+...@googlegroups.com.

Daniel Widdis

unread,
Mar 14, 2021, 1:44:32 PM3/14/21
to jna-...@googlegroups.com

Uh, ignore that.  By making foo static it’s always, forever, in scope.  Doh!

    --

    You received this message because you are subscribed to the Google Groups "Java Native Access" group.

    To unsubscribe from this group and stop receiving emails from it, send an email to jna-users+...@googlegroups.com.

Daniel Widdis

unread,
Mar 14, 2021, 1:49:50 PM3/14/21
to jna-...@googlegroups.com

OK, same question, with a better example.  I don’t see Foo going out of scope, which should trigger the finalize to switch bar back to false.

 

Output:

 

After instantiation, bar is true

After finalize, bar is false

Before call, bar is true

During call bar is true

After call bar is true

 

Code:

 

public class Test {

 

    static boolean bar = false;

 

    static class Foo {

        public int baz = 42;

 

        public Foo() {

            bar = true;

        }

 

        @Override

        protected void finalize() {

            bar = false;

        }

    }

 

    static boolean scopeTest() {

        Foo foo = new Foo();

        System.out.println("After instantiation, bar is " + bar);

        foo = null;

        System.gc();

        System.gc();

        Util.sleep(2000);

        System.out.println("After finalize, bar is " + bar);

        foo = new Foo();

        System.out.println("Before call, bar is " + bar);

        return tailCall(foo.baz);

    }

 

    private static boolean tailCall(int baz) {

        // has the scopeTest() method ended yet?

        System.gc();

        System.gc();

        Util.sleep(2000);

        System.gc();

        System.gc();

        System.out.println("During call bar is " + bar);

        return bar;

    }

 

    public static void main(String[] args) {

        System.out.println("After call bar is " + scopeTest());

    --

    You received this message because you are subscribed to the Google Groups "Java Native Access" group.

    To unsubscribe from this group and stop receiving emails from it, send an email to jna-users+...@googlegroups.com.

Neil C Smith

unread,
Mar 14, 2021, 2:24:21 PM3/14/21
to jna-...@googlegroups.com
On Sun, 14 Mar 2021 at 17:42, Daniel Widdis <wid...@gmail.com> wrote:
> Our fix(es) are based on the idea that the variable "user" has gone out of scope ... every reference I see indicates scope lasts until the trailing curly brace

Don't assume scope has any bearing on garbage collection though. A
Java object can be garbage collected before its constructor has even
completed. The discussions that led to the introduction of
Reference.reachabilityFence are an interesting rabbit warren to
explore.

Best wishes,

Neil

--
Neil C Smith
Codelerity Ltd.
www.codelerity.com

Codelerity Ltd. is a company registered in England and Wales
Registered company number : 12063669
Registered office address : Office 4 219 Kensington High Street,
Kensington, London, England, W8 6BD

Matthias Bläsing

unread,
Mar 14, 2021, 2:34:29 PM3/14/21
to jna-...@googlegroups.com
Hi Daniel,

Am Sonntag, den 14.03.2021, 10:42 -0700 schrieb Daniel Widdis:

OK, I'm still confused here. 

 

Our fix(es) are based on the idea that the variable "user" has gone out of scope in this line because we’re passing on a reference not attached to “user”:

 

return getAccountBySid(user.User.Sid);

 

Initially I thought this was associated with the method "ending" with the return call, like tail recursion.   However, as I've read more, Java does not do any optimization for tail recursion and every reference I see indicates scope lasts until the trailing curly brace.  I wrote this test class which seems to confirm the variable is still in scope throughout the return call:

 


testing with simple programms will not really help, because you need to convince the GC to actually do the work (there is not interface to the GC and System#gc is only an advise, which can be ignored).

In the above sample, multiple calls are present:

1. getField für user.User
2. getField für user.User.Sid
3. invoke method für getAccountBySid

Even without reorderings is user eligable for GC after 1, the object stored in user.User after 2.

The fix in https://github.com/java-native-access/jna/pull/1326 relies on PSid (the structure behind user.User.Sid) holding an additional strong reference on the Memory object, that backs TOKEN_USER. The strong reference comes from SharedMemory, which is an inner class of Memory - as the class is not static, it will retain the reference of the object it was created from.

HTH

Matthias

Daniel Widdis

unread,
Mar 14, 2021, 2:49:22 PM3/14/21
to jna-...@googlegroups.com
Thank you -- this was the key insight I needed to understand, that GC is not tied strongly to scope.

I knew the fix was a good thing in general, just not why in this particular case!
--
You received this message because you are subscribed to the Google Groups "Java Native Access" group.
To unsubscribe from this group and stop receiving emails from it, send an email to jna-users+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/jna-users/CAPxOS5GdBReKFU0kxObOizZJz3%2BuNsmvmAwAjAtDwH6REBnicQ%40mail.gmail.com.


Daniel Widdis

unread,
Mar 14, 2021, 2:53:08 PM3/14/21
to jna-...@googlegroups.com
Seems the magic word for GC is "reachable".

https://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.6.1-200

This bug/fix has been very educational for me!

On 3/14/21, 11:24 AM, "Neil C Smith" <jna-...@googlegroups.com on behalf of ne...@codelerity.com> wrote:

--
You received this message because you are subscribed to the Google Groups "Java Native Access" group.
To unsubscribe from this group and stop receiving emails from it, send an email to jna-users+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/jna-users/CAPxOS5GdBReKFU0kxObOizZJz3%2BuNsmvmAwAjAtDwH6REBnicQ%40mail.gmail.com.


Durchholz, Joachim

unread,
Mar 15, 2021, 4:52:49 AM3/15/21
to jna-...@googlegroups.com

Going out of scope does not necessarily trigger finalization.

Actually, finalization may never run at all. It’s purely advisory; if you want any guarantees, you need other mechanisms, and even these aren’t guaranteed to be run at any particular point in time.

 

If you wish to guarantee that a specific action is run, you have to call it from normal code.

If you wish to run it when going out of scope, your only (and actually quite good) option is try-with-resources.

 

Regards,

Jo

 

 

Sensitivity: C2 Internal

From: jna-...@googlegroups.com <jna-...@googlegroups.com> On Behalf Of Daniel Widdis


Sent: Sonntag, 14. März 2021 18:50
To: jna-...@googlegroups.com

Reply all
Reply to author
Forward
0 new messages