Categories
PHP Uncategorized Zend

Hacking PHP to allow redeclaration of functions. (I)

It is often said that a developer of some language is able to write that language in any language. I can tell you this is a great true.

Currently I am in a project which uses the PHP technology and when testing it PHP does nothing but get into my way when trying to write Perl in PHP not allowing me to redeclare procedural functions.

I have seen responses in like this https://stackoverflow.com/questions/1244949/mocking-php-functions-in-unit-tests which recommend the usage of runkit, but I thought it would be could to get into the PHP code and try to implement such a thing.

The PHP code is a pretty large codebase so searching for this concrete restriction would be hard, but I have a hint given by the own PHP, let’s run this:

sergio@bahdder ~/php-8.0.3 $ php -r 'function a(){} function a(){ print "hello";} a();'
PHP Fatal error:  Cannot redeclare a() (previously declared in Command line code:1) in Command line code on line 1

Let’s search this message and try to get PHP do what I mean just for learning how the function declaration works in the PHP source code.

rg 'Cannot redeclare'

Ups, too much output… Let’s try again stripping some directories from the rg used in test which right now are not interesting.

rg 'Cannot redeclare' --glob '!Zend/tests' --glob '!tests'

That’s is better:

Zend/zend_compile.c
1065:           zend_error_noreturn(error_level, "Cannot redeclare %s() (previously declared in %s:%d)",
1070:           zend_error_noreturn(error_level, "Cannot redeclare %s()",
6499:                           zend_error_noreturn(E_COMPILE_ERROR, "Cannot redeclare %s::$%s",
6817:           zend_error_noreturn(E_COMPILE_ERROR, "Cannot redeclare %s::%s()",
7064:                   zend_error_noreturn(E_COMPILE_ERROR, "Cannot redeclare %s::$%s",
7658:                           "Cannot redeclare constant '%s'", ZSTR_VAL(unqualified_name));

Zend/zend_inheritance.c
1038:                           zend_error_noreturn(E_COMPILE_ERROR, "Cannot redeclare %s%s::$%s as %s%s::$%s",

ext/opcache/zend_accelerator_util_funcs.c
468:            zend_error(E_ERROR, "Cannot redeclare %s() (previously declared in %s:%d)",
473:            zend_error(E_ERROR, "Cannot redeclare %s()", ZSTR_VAL(function1->common.function_name));
512:            zend_error(E_ERROR, "Cannot redeclare %s() (previously declared in %s:%d)",
517:            zend_error(E_ERROR, "Cannot redeclare %s()", ZSTR_VAL(function1->common.function_name));

That gives me a much better hint, Zend/zend_compile.c and ext/opcache/zend_accelerator_util_funcs.c can be the cause of this, let’s see how them work starting by Zend/zend_compile.c.

I got that function:

static zend_never_inline ZEND_COLD ZEND_NORETURN void do_bind_function_error(zend_string *lcname, zend_op_array *op_array, zend_bool compile_time) /* {{{ */
{
    zval *zv = zend_hash_find_ex(compile_time ? CG(function_table) : EG(function_table), lcname, 1);
    int error_level = compile_time ? E_COMPILE_ERROR : E_ERROR;
    zend_function *old_function;

    ZEND_ASSERT(zv != NULL);
    old_function = (zend_function*)Z_PTR_P(zv);
    if (old_function->type == ZEND_USER_FUNCTION
        && old_function->op_array.last > 0) {
        zend_error_noreturn(error_level, "Cannot redeclare %s() (previously declared in %s:%d)",
                    op_array ? ZSTR_VAL(op_array->function_name) : ZSTR_VAL(old_function->common.function_name),
                    ZSTR_VAL(old_function->op_array.filename),
                    old_function->op_array.opcodes[0].lineno);
    } else {
        zend_error_noreturn(error_level, "Cannot redeclare %s()",
            op_array ? ZSTR_VAL(op_array->function_name) : ZSTR_VAL(old_function->common.function_name));
    }
}

This function is likely called when PHP reachs an state where function redeclaration is done, but the purpose is only logging that and fail, I will delete this method to figure out what breaks in compilation, I am likely not wanting this function anymore anyway.

Shamelessly I run make -j4 knowing this is not going to compile anymore.

/home/sergio/php-8.0.3/Zend/zend_compile.c: In function ‘do_bind_function’:
/home/sergio/php-8.0.3/Zend/zend_compile.c:1063:3: warning: implicit declaration of function ‘do_bind_function_error’; did you mean ‘do_bind_function’? [-Wimplicit-function-declaration]
 1063 |   do_bind_function_error(Z_STR_P(lcname), NULL, 0);
      |   ^~~~~~~~~~~~~~~~~~~~~~
      |   do_bind_function

Ok, let’s see what is happening here.

I got into the do_bind_function it looks promising:

ZEND_API zend_result do_bind_function(zval *lcname) /* {{{ */
{
    zend_function *function;
    zval *rtd_key, *zv;

    rtd_key = lcname + 1;
    zv = zend_hash_find_ex(EG(function_table), Z_STR_P(rtd_key), 1);
    if (UNEXPECTED(!zv)) {
        do_bind_function_error(Z_STR_P(lcname), NULL, 0);
        return FAILURE;
    }
    function = (zend_function*)Z_PTR_P(zv);
    if (UNEXPECTED(function->common.fn_flags & ZEND_ACC_PRELOADED)
            && !(CG(compiler_options) & ZEND_COMPILE_PRELOAD)) {
        zv = zend_hash_add(EG(function_table), Z_STR_P(lcname), zv);
    } else {
        zv = zend_hash_set_bucket_key(EG(function_table), (Bucket*)zv, Z_STR_P(lcname));
    }
    if (UNEXPECTED(!zv)) {
        do_bind_function_error(Z_STR_P(lcname), &function->op_array, 0);
        return FAILURE;
    }
    return SUCCESS;
}

This is not Kansas anymore, a good bunch of strange macros and functions that are not familiar to me are here, like UNEXPECTED or EG, but PHP developer were enough kind to make good variable names and functions so I maybe be able to tweak a little the code.

Maybe silencing the error is enough…

ZEND_API zend_result do_bind_function(zval *lcname) /* {{{ */
{    
    zend_function *function;
    zval *rtd_key, *zv;

    rtd_key = lcname + 1;
    zv = zend_hash_find_ex(EG(function_table), Z_STR_P(rtd_key), 1);
    function = (zend_function*)Z_PTR_P(zv);
    if (UNEXPECTED(function->common.fn_flags & ZEND_ACC_PRELOADED)
            && !(CG(compiler_options) & ZEND_COMPILE_PRELOAD)) {
        zv = zend_hash_add(EG(function_table), Z_STR_P(lcname), zv);
    } else {
        zv = zend_hash_set_bucket_key(EG(function_table), (Bucket*)zv, Z_STR_P(lcname));
    }
    return SUCCESS;
}

mkdir b && make -j4 && INSTALL_ROOT=b make install

But there are more calls to do_bind_error:

    if (toplevel) {
        if (UNEXPECTED(zend_hash_add_ptr(CG(function_table), lcname, op_array) == NULL)) {
            do_bind_function_error(lcname, op_array, 1);
        }
        zend_string_release_ex(lcname, 0);
        return;
    }

This gives me bad feelings, I don’t think this == NULL means the key is updated anyway, maybe I supposed too much, let’s look what it does, I may have to go back and rethink all the previous changes.

static zend_always_inline void *zend_hash_add_ptr(HashTable *ht, zend_string *key, void *pData)
{
    zval tmp, *zv;

    ZVAL_PTR(&tmp, pData);
    zv = zend_hash_add(ht, key, &tmp);
    if (zv) {
        ZEND_ASSUME(Z_PTR_P(zv));
        return Z_PTR_P(zv);
    } else {
        return NULL;
    }
}

Let’s look into zend_hash_add…

Ups I made too much assumptions…

ZEND_API zval* ZEND_FASTCALL zend_hash_add(HashTable *ht, zend_string *key, zval *pData)
{
    return _zend_hash_add_or_update_i(ht, key, pData, HASH_ADD);
}

ZEND_API zval* ZEND_FASTCALL zend_hash_update(HashTable *ht, zend_string *key, zval *pData)
{
    return _zend_hash_add_or_update_i(ht, key, pData, HASH_UPDATE);
}

ZEND_API zval* ZEND_FASTCALL zend_hash_update_ind(HashTable *ht, zend_string *key, zval *pData)
{
    return _zend_hash_add_or_update_i(ht, key, pData, HASH_UPDATE | HASH_UPDATE_INDIRECT);
}

ZEND_API zval* ZEND_FASTCALL zend_hash_add_new(HashTable *ht, zend_string *key, zval *pData)
{
    return _zend_hash_add_or_update_i(ht, key, pData, HASH_ADD_NEW);
}

If there is a update it is clear that add won’t update, I also found a interesting function:

ZEND_API zval* ZEND_FASTCALL zend_hash_add_or_update(HashTable *ht, zend_string *key, zval *pData, uint32_t flag)
{
    if (flag == HASH_ADD) {
        return zend_hash_add(ht, key, pData);
    } else if (flag == HASH_ADD_NEW) {
        return zend_hash_add_new(ht, key, pData);
    } else if (flag == HASH_UPDATE) {
        return zend_hash_update(ht, key, pData);
    } else {
        ZEND_ASSERT(flag == (HASH_UPDATE|HASH_UPDATE_INDIRECT));
        return zend_hash_update_ind(ht, key, pData);
    }
}

Unfortunatelly this means the bitmask the flag contains. (I looked at it without you cannot be used to tell the hashtable to do insert or update whatever it needs, let’s look in the private method they all call to see if it is true what I am thinking.

static zend_always_inline zval *_zend_hash_str_add_or_update_i(HashTable *ht, const char *str, size_t len, zend_ulong h, zval *pData, uint32_t flag)
{
    zend_string *key;
    uint32_t nIndex;
    uint32_t idx;
    Bucket *p;

    IS_CONSISTENT(ht);
    HT_ASSERT_RC1(ht);

    if (UNEXPECTED(HT_FLAGS(ht) & (HASH_FLAG_UNINITIALIZED|HASH_FLAG_PACKED))) {
        if (EXPECTED(HT_FLAGS(ht) & HASH_FLAG_UNINITIALIZED)) {
            zend_hash_real_init_mixed(ht);
            goto add_to_hash;
        } else {
            zend_hash_packed_to_hash(ht);
        }
    } else if ((flag & HASH_ADD_NEW) == 0) {
        p = zend_hash_str_find_bucket(ht, str, len, h);

        if (p) {
            zval *data;

            if (flag & HASH_ADD) {
                if (!(flag & HASH_UPDATE_INDIRECT)) {
                    return NULL;
                }
                ZEND_ASSERT(&p->val != pData);
                data = &p->val;
                if (Z_TYPE_P(data) == IS_INDIRECT) {
                    data = Z_INDIRECT_P(data);
                    if (Z_TYPE_P(data) != IS_UNDEF) {
                        return NULL;
                    }
                } else {
                    return NULL;
                }
            } else {
                ZEND_ASSERT(&p->val != pData);
                data = &p->val;
                if ((flag & HASH_UPDATE_INDIRECT) && Z_TYPE_P(data) == IS_INDIRECT) {
                    data = Z_INDIRECT_P(data);
                }
            }
            if (ht->pDestructor) {
                ht->pDestructor(data);
            }
            ZVAL_COPY_VALUE(data, pData);
            return data;
        }
    }

    ZEND_HASH_IF_FULL_DO_RESIZE(ht);        /* If the Hash table is full, resize it */

add_to_hash:
    idx = ht->nNumUsed++;
    ht->nNumOfElements++;
    p = ht->arData + idx;
    p->key = key = zend_string_init(str, len, GC_FLAGS(ht) & IS_ARRAY_PERSISTENT);
    p->h = ZSTR_H(key) = h;
    HT_FLAGS(ht) &= ~HASH_FLAG_STATIC_KEYS;
    ZVAL_COPY_VALUE(&p->val, pData);
    nIndex = h | ht->nTableMask;
    Z_NEXT(p->val) = HT_HASH(ht, nIndex);
    HT_HASH(ht, nIndex) = HT_IDX_TO_HASH(idx);

    return &p->val;
}

This means that if I can make this function get HASH_ADD | HASH_UPDATE_INDIRECT I may be able to update the hash table, let’s continue later, this was rough, but I am starting catching concepts.

Categories
Uncategorized

If the code cannot be read it is not trustworthy software.

Often in the malware world one of the most important steps is the process to obfuscate the malicious software so investigators have a hard time guessing how to fight against it’s resilence and it’s goals.

The same happens with privative of freedom software, often the goal obfuscating the code it is not only because you want to protect your “intelectual property” but because you want to avoid investigators guessing how much and how your application surveillances it’s users.

Other common reason is you know your code is insecure, it is difficult/expensive to solve and you want to avoid people knowing it at the first look.

You or your users eventually will be cracked, but with some luck no one will know it, only hoping the cracker only wants the information and do not take down the service so people start making questions.

Of course there is also selfishness, people will depend of my solution because once they start using it they will have a really difficult time if they want to migrate out because they will have their data adapted to my privative format or tied to bussiness specific features that they will have to recreate in whatever new system they want to use.

Only if you can free your code you can prove your solution is really trustworthy or not, people will be able to tell it after reading your code.

Categories
Uncategorized

Avoid the meme and avoid the expression “User level” in your resume.

Let’s put it clear everyone who uses a computer or smartphone is a user, that combined with the widespread computer analphabetism mades a very bad combination to put “User level” on informatics on your resume

I think a better approach would be to indicate you can use a computer without receiving continuous support for the most basic things and that you can learn to use new tools with ease.

Also would be a great addition to know that you know how to use most basic security measures like do not installing software that is not trustworthy or safe (Like Windows or Microsoft Office…) without asking to IT support, you are able to guess something may be an attempt to trick you to get your password and you know how to manage you credentials in a safe manner like using a password manager.

Software sometimes fails or needs new features so a valuated for IT persons ability in users is being able to report the bugs or needed features in a complete and detailed way.

Lets hear what users think about this. Feature or bug?

Categories
Uncategorized

Libre software does not need a subsidy, Libre software needs compromise.

I read a article saying how much money the libre software makes the goverments in the European Union save and that EU wants to promote the development of libre software and that is great!

But what most contributors to libre software really want is the goverment adopting it and making their own software libre software, public money, public code we say.

It is true that Richard Stallman said that if all software should be libre a tax could be setup to finance the software development which I think is a great idea, if we were forced to pay to the SGAE the Digital Canon for piracy they did not know if we were going to do.

Why would not pay for libre software we are already using everyday like the Linux kernel, busybox, libreoffice. You may be able to use a computer without privative software today, and maybe soon also a smartphone, but it is impossible to use nowdays something that connects to internet today without taking advantage of thousands of libre software programs like the Linux or BSD your router is using, the Apache or Nginx webserver you connect to or the modified version of the XMPP protocol Whatsapp uses.

Libre software is deeply installed into the heart of internet, and that is not free as in beer, and it is not developed by hobbist, but for the best developers of the world.

If you want to help libre software it think the best great first step you can do is embrace its philosophy yourself and let your software to be free as in freedom.

Categories
Uncategorized

Libre software for a fair society!!

In a world where software can determine if you are elegible for a job, deny you to rent a apartament or make you suspicious for the police we as citizens need to know which criteria is being used for those purposes, if code is law we should at least be able to read it and adapt it to make it the fairest for everybody.

There is no way goverments can pay millions of euros/dollars in software which the citizens have no right to read, execute, modify and redistribute, software that determines their lifes every day and sometimes goverment makes mandatory to install for citizens to execute their rights.

We need libre software not only to get a free society, but also to audit and improve the fairness of our society.

Categories
Uncategorized

Managing projects under the AGPLv3.

There is an increasing amount of web projects licensed under the AGPLv3 which requires you to convey the source code to your users, this makes you to have to act smart to always convey the latest source code, here is how I do it.

For example we will try to convey the Pleroma source code to our users, here is how are we going to do it.

First in our user on our computer we clone the repository:

git clone https://git.pleroma.social/pleroma/pleroma Pleroma
cd Pleroma

Then we checkout to the branch we are going to use, in this case stable:

git checkout stable

Then we create an empty repo on the server in this case I am going to use my gitea the url will be: ssh://git@gitea.sergiotarxz.freemyip.com:22099/sergiotarxz/p.git

And we push the changes to this repository:

git remote add downstream ssh://git@gitea.sergiotarxz.freemyip.com:22099/sergiotarxz/p.git
git push -u downstream HEAD

Now we can change things and commit them to this repository, in the server while building from source we will do:

git clone ssh://git@gitea.sergiotarxz.freemyip.com:22099/sergiotarxz/p.git Pleroma
cd Pleroma

Happy compilation!!

Categories
Uncategorized

Programming colors in smartphone.

Are real the advantages of using a dark theme while programming?

Is often read that dark theme is better for your eyes, but this does not have why to be true if you use the computer in a well illuminated room.

It is true is a know standard to have the terminal on white text over dark background, but when using for example Termux under the sun light the advantages of using a dark theme are no longer so real.

I think if you use the terminal in a smartphone is often more suitable to have it with a light background to be able to see better under the sun light.

I think this should be also matter of user choice, not a single style solution can suit all users, having only a way to see for example a webpage is a accesibility issue by itself since some disabilities/users have needs which are direct opposite between them.

Categories
Uncategorized

Getting a job as a premium service.

About the moral dimension of the increasing tendency to offer services with premium paid features for people seeking for a job.

Due to my recent unmployment status I have registered a good bunch of job seeking platforms all of them share offering premium features for a prize to candidates.

I think this want to play with the need to get a job to get money from the unprivileged users who want a job, Linkedin itself is and promotes such services in some of the offers, many times you have no other option than register in one or more o those services to apply for a job.

“Premium users will appear before”, “Premium users are x% more likely to get a job” are some of the quotes you can find on those sites.

People seeking for a job on those platforms are tricked into paying without any warranty this will help them at all getting a job.

I get Linkedin offering premium features for recluiters since they are using Linkedin itself to get money, but I think doing the same with candidates is power abuse.

I think we should advocate for descentralization and submiting resumes without registration on sites to make a fair employment search system.