secure-software-engineering / FlowDroid

FlowDroid Static Data Flow Tracker
GNU Lesser General Public License v2.1
1.02k stars 293 forks source link

Callback edges #593

Closed dschm1dt closed 1 year ago

dschm1dt commented 1 year ago

Hello, I tried to analyze apps with FlowDroid. However, it does not find the flows I am looking for since it considers them unreachable. The issue is that the apps use libraries with "callback" methods (sources) which FlowDroid considers unreachable; consequently, it finds no sources. I saw the issue #335, which looks similar. However, only adding an edge from enqueue(okhttp3.Callback) to onResponse does not seem to help. Do you have any further suggestions?

I provided an example below with my sources/sinks and the soot edge config I tried to add. Additionally, I uploaded the apk,:

    public static OkHttpClient client = new OkHttpClient();

    private void getRequest(String url) throws Exception {
        Request request = new Request.Builder()
                .url(url)
                .build();

        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onResponse(Call call, Response response) {
                try {
                    Log.d("onResponse", response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onFailure(Call call, IOException ex) {
                System.out.println("exception");
                ex.printStackTrace();
            }
        });
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        try {
            getRequest("https://google.com/");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
<okhttp3.Callback: void onResponse(okhttp3.Call,okhttp3.Response)> -> _SOURCE_
<android.util.Log: int d(java.lang.String,java.lang.String)> -> _SINK_
    <edge>
        <source invoketype="instance"
            subsignature="okhttp3.Call void enqueue(okhttp3.Callback)" />
        <targets>
            <direct
                subsignature="okhttp3.Callback void onResponse(okhttp3.Call,okhttp3.Response)"
                target-position="argument" index="1" />
            <direct
                subsignature="okhttp3.Callback void onFailure(okhttp3.Call,java.io.IOException)"
                target-position="argument" index="1" />
        </targets>
    </edge>
StevenArzt commented 1 year ago

How did you configure your sources? FlowDroid's distinguishes two types of callback sources. Firstly, there are parameters of callbacks triggered by the Android runtime for external events that happen at arbitrary times while the host component is in its "running" state, e.g., a GPS position change. FlowDroid will integrate calls to these methods into its dummy main method and consider the parameters as sensitive.

Secondly, there are callbacks that we bind to method calls that indirectly trigger them, as in your case. These callback methods are not called from the dummy main method. Instead, FlowDroid relies on Soot's virtual edge summaries (virtualedges.xml). The parameters of these methods are not automatically sources for the data flow analysis. You need to configure manually which parameters of which methods you want as sources.

Note that this is only possible with our XML-based source/sink definition language, because the simple text file SourcesAndSinks.txt is not expressive enough. Here's an example:

<sinkSources  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:schemaLocation="SourcesAndSinks.xsd">
    <category id="NO_CATEGORY" customId="WHATEVER" description="Something goes here">
        <method signature="android.content.SharedPreferences$OnSharedPreferenceChangeListener: void onSharedPreferenceChanged(android.content.SharedPreferences,java.lang.String)" callType="callback">
            <param index="1" description="Key">
                <accessPath isSource="true" isSink="false" />
            </param>
        </method>
    </category>
</sinkSources>

Note the callType attribute. If you omit it, the default is a normal method call where you usually reference the return value as a source. There are also some smaller examples under soot-infoflow-android/testXmlParser.

StevenArzt commented 1 year ago

By the way, you can also configure FlowDroid to treat the environment-based callbacks the same way, i.e., only consider them as sources if explicitly listed in the source/sink definition file. The "take all of them as sources" behavior is mainly for making things easy for users and for retaining backwards compatibility.

dschm1dt commented 1 year ago

Thanks for the response and clarification. I tried configuring sources and sinks as XML and a normal text file, however, without success.

It looks like the onResponse method is considered unreachable to me. If I execute the following line forwardProblem.getManager().getICFG().isReachable(Scene.v().getMethod("<com.example.okhttp3_callback.MainActivity$1: void onResponse(okhttp3.Call,okhttp3.Response)>").retrieveActiveBody().getThisUnit()) in soot.jimple.infoflow.AbstractInfoflow.scanMethodForSourcesSinks I get false returned.

<sinkSources  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:schemaLocation="SourcesAndSinks.xsd">
    <category id="NO_CATEGORY" customId="WHATEVER" description="Something goes here">
        <method signature="okhttp3.Callback: void onResponse(okhttp3.Call,okhttp3.Response)" callType="callback">
            <param index="1" description="Key">
                <accessPath isSource="true" isSink="false" />
            </param>
        </method>

        <method signature="android.util.Log: int d(java.lang.String,java.lang.String)">
            <param index="1" type="java.lang.String">
                <accessPath isSource="false" isSink="true">
                </accessPath>
            </param>
        </method>
    </category>

flowdroid output:

[main] INFO soot.jimple.infoflow.android.SetupApplication$InPlaceInfoflow - Looking for sources and sinks...
[main] ERROR soot.jimple.infoflow.android.SetupApplication$InPlaceInfoflow - No sources found, aborting analysis
StevenArzt commented 1 year ago

This is interesting, because you seem to be missing the method in the callgraph. I wonder why your callgraph check is so complex, though. You could also have checked the method directy via the CG obtained from the Soot scene (getReachableMethods). You may want to do this just to be sure that it's not a problem with the ICFG.

Please also make sure that you are using the newest Soot develop branch. The current version already contains an entry for the okhttp methods in its virtualedges.xml. The file in the repository also references argument 0 instead of 1 as in your code. Argument index 0 looks right to me, because the enqueue method receives the callback in its first parameter (yes, the semantics of the file are slightly confusing).

Further, the current code also supports a check on the declaring class, making the analysis more precise in comparison to only checking subsignatures.

dschm1dt commented 1 year ago

With the development version and the latest snapshots of soot the callback method is reachable, and it finds the flow. Just changing the virtualedges.xml in an older version to the one from the development branch did not.

By changing to the development build, an ArrayIndexOutOfBoundsException is thrown in InfoflowProblem line 609 (originalCallArg = iCallStmt.getInvokeExpr().getArg(isReflectiveCallSite ? 1 : i);), since the callee.getParameterCount() > iCallStmt.getInvokeExpr().getArgCount(): callee = <com.example.okhttp3_callback.MainActivity$1: void onResponse(okhttp3.Call,okhttp3.Response)> iCallStmt = interfaceinvoke $r6.<okhttp3.Call: void enqueue(okhttp3.Callback)>($r2)

I fixed it by checking if the index fits the iCallStmt argument length and created a merge request.

StevenArzt commented 1 year ago

I just merged your MR. Btw, one of my students is looking into a more generic solution for filtering fake edges from callback summaries when mapping taint abstractions in call edges. They might have matching parameter numbers, but that doesn't mean that a 1:1 mapping is correct.